問題:
給定任意一個正整數,求比這個數大且最小的“不重復數”,“不重復數”的含義是相鄰兩位不相同,例如1101是重復數,而1201是不重復數。
——引自 百度2014校招筆試題目題解
問題的提法:
為代碼簡便,將問題等價地改為,求大於等於指定正整數的不重復數。由find()函數實現。
調用:
find( i + 1u )
原型:
unsigned find( unsigned );
算法:
以19922884u為例。
首先確定高位是否是重復數。即依次判斷
1u
19u
199u
1992u
19922u
199228u
1992288u
是否是重復數。
如高位不是重復數,則當前數不變,並判斷當前數是否是重復數。
例如對19u,由於1u不是重復數(一位正整數不是重復數,是顯而易見的事。(if ( n < 10u ) return n;),所以判斷19u是否是重復數(通過簡單地判斷19u的個位和十位是否相同。n % 10u == n /10u %10u)。
當當前數為199u時,高位(19u)不是重復數,當前數本身(119u)是重復數。
此時,將當前數加1,問題變為求大於等於200u的不重復數。
由於200u的高位不是重復數,而200u本身是重復數,所以經過了
n:2
n:20
之后,問題變成了求大於等於201u的不重復數。
201u不是重復數,所以回到求大於等於1992u的不重復數時,由於對於1992u來說,由於高位是重復數(返回值大於1992u/10u。 if ( n/10u <(t = find( n/10u)) )n = t * 10;),所以問題變成了求2010u的不重復數(n = t * 10;)。
2010u是不重復數,求大於等於19922u的不重復數變成了求大於等於20100u的不重復數。
但由於20100u的高位不是重復數,20100u本身是重復數(個位和十位相同),所以問題又變成了求大於等於20101u的不重復數的問題( if ( n % 10u == n /10u %10u ) return find( n + 1u );)。
重復以上過程,可得結果為20101010。
代碼:
1 #include <stdio.h> 2 3 unsigned find( unsigned ); 4 5 int main( void ) 6 { 7 unsigned i ; 8 9 //測試 10 for ( i = 19922884u ; i < 19922884u + 1u ; i++ ) 11 { 12 printf ( "%u %u\n" , i , find( i + 1u ) ); 13 } 14 15 return 0; 16 } 17 18 unsigned find( unsigned n ) 19 { 20 unsigned t; 21 22 printf( "n:%u\n" , n ) ; //演示一下調用路徑 23 24 if ( n < 10u ) 25 return n; 26 27 if ( n / 10u < ( t = find( n / 10u ) ) ) 28 n = t * 10u ; 29 30 if ( n % 10u == n /10u % 10u ) 31 return find( n + 1u ); 32 33 return n ; 34 }
提前回復:
飛鳥_Asuka 網友大概又會問:“有沒有不用遞歸的算法呢?”我提前回復,有。不過目前寫得還很難看,實在拿不出手。等我改好了再拿出來獻丑。
非遞歸寫法
#include <stdio.h> typedef struct { unsigned char t[ 20 ] ; //這個空間應該夠了 int top ; //記錄第一位的下標 } Map ; unsigned find( unsigned ); void parse( Map * , unsigned ) ; int search ( const Map * ); void add_1( Map * , const int ); void clear( Map * , const int , const int ); unsigned combi( const Map * ); int main( void ) { unsigned iu ; //測試 for ( iu = 19922884u ; iu < 19922884u + 1u ; iu++ ) { printf ( "%u %u\n" , iu , find( iu + 1u ) ); } return 0; } unsigned combi( const Map * p_m ) { unsigned nu = 0u ; int i; for ( i = p_m->top ; i >= 0 ; i -- ) { nu *= 10u ; nu += p_m->t[i] ; } return nu; } void clear( Map * p_m , const int from , const int to ) { int i ; for ( i = from - 1 ; i > to - 1; i -- ) p_m->t[i] = 0u ; } void add_1( Map * p_m , const int from ) { int i ; p_m->t[from] ++; //最低位加1 for ( i = from ; i < p_m->top ; i ++ ) //進位處理 { p_m->t[i + 1] += p_m->t[i] / 10u ; p_m->t[i] %= 10u ; } if ( p_m->t[p_m->top] > 9u ) //最高位有進位 { p_m->t[p_m->top + 1] = p_m->t[p_m->top] / 10u ; p_m->t[p_m->top ++ ] %= 10u ; } } int search ( const Map * p_m ) { int i ; for ( i = p_m->top ; i > 0 ; i-- ) { if ( p_m->t[i] == p_m->t[i-1] ) break ; } return i - 1 ; } void parse( Map * p_m , unsigned n ) { p_m->top = -1 ; while ( n > 0u ) { p_m->t[ ++ p_m->top ] = n % 10u ; n /= 10u ; } } unsigned find( unsigned n ) { Map map ; int end = 0 , b_point ; parse( &map , n ) ; //將n分解為數字 while ( ( b_point = search ( &map ) ) > -1 )//為-1時說明不是重復數 { add_1( &map , b_point ); //重復數部分加1 clear( &map , b_point , end ); //后面改為0 end = b_point ; //確定下次循環的處理范圍 } return combi( &map ); }
算法描述:
以19922884為例,
數據結構:用一數組及所使用到的最大下標表示。
map:
4 8 8 2 2 9 9 1
7
find():
從19922884的高位開始查找不重復數,記錄位置
b_point = search ( &map )
b_point :5
b_point以后部分加1:add_1( &map , b_point );
map:
4 8 8 2 2 0 0 2
7
從 end到b_point-1之間的元素清零:clear( &map , b_point , end );
map:
0 0 0 0 0 0 0 2
7
記錄b_point作為下次循環的end。
end:5
第二次循環,
b_point = search ( &map )
b_point :5
b_point以后部分加1:add_1( &map , b_point );
map:
0 0 0 0 0 1 0 2
7
從 end到b_point-1之間的元素清零:由於此時end為5,所以沒有任何元素清零
記錄b_point作為下次循環的end。
end:5
第三次循環,
b_point = search ( &map )
b_point :3
b_point以后部分加1:add_1( &map , b_point );
map:
0 0 0 1 0 1 0 2
7
從 end到b_point-1之間的元素清零:由於此時end為5,b_point 為3,所以沒有任何元素清零
記錄b_point作為下次循環的end。
end:3
第四次循環,
b_point = search ( &map )
b_point :1
b_point以后部分加1:add_1( &map , b_point );
map:
0 1 0 1 0 1 0 2
7
從 end到b_point-1之間的元素清零:由於此時end為3,b_point 為1,所以沒有任何元素清零
記錄b_point作為下次循環的end。
end:1
第五次循環,
b_point = search ( &map )
b_point :-1
循環結束。
重新合成整數:return combi( &map );
問題得解。
總結
非遞歸寫法很難寫,就這個問題而言,效率方面也不比遞歸方法好。