問題:
給定任意一個正整數,求比這個數大且最小的“不重復數”,“不重復數”的含義是相鄰兩位不相同,例如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 );
問題得解。
總結
非遞歸寫法很難寫,就這個問題而言,效率方面也不比遞歸方法好。
