算法:求比指定數大且最小的“不重復數”問題的高效實現


問題:

  給定任意一個正整數,求比這個數大且最小的“不重復數”,“不重復數”的含義是相鄰兩位不相同,例如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:

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 );

問題得解。

總結

  非遞歸寫法很難寫,就這個問題而言,效率方面也不比遞歸方法好。

后續:對Alexia(minmin)網友代碼的評論及對“求比指定數大且最小的‘不重復數’問題”代碼的改進


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM