對於“大於指定正整數的最小“不重復數”問題”,最初,在 算法:求比指定數大且最小的“不重復數”問題的高效實現 中,我給出了一個遞歸寫法,之后在同一篇博文中給出了一個非遞歸寫法。
后來在 對Alexia(minmin)網友代碼的評論及對“求比指定數大且最小的‘不重復數’問題”代碼的改進 中對我的寫法進行了更詳細的說明,並進行了重要改進。使之適合范圍更大。
在 評playerc網友的"求比指定數大且最小的“不重復數”問題" 中評論了playerc的寫法。
playerc的思路沒問題,但代碼復雜且存在錯誤。現在我按照這個思路給出我的寫法。
詳細的分析求解過程,就不在正文中描述了。代碼中有詳盡的注釋,在前幾篇博文中也進行了說明。這次寫法與前面的主要不同點的是,實現了只一次加0101……。
1 #include <stdio.h> 2 #include <stdlib.h> 3 4 #define SIZE(x) (sizeof (x) / sizeof (x)[0]) 5 #define BE_DIG(x) ( '0' <= x && x <= '9' ) 6 #define MAX 128 7 8 typedef struct 9 { 10 unsigned char digs[ MAX + 1 ] ;//從低位到高位存儲各位數字 11 //並為可能發生的進位預留一個位置 12 unsigned char * p_high ; //記錄最高位數字位置 13 } 14 Number ; 15 16 void input( Number * ); 17 void reverse( unsigned char * , unsigned char * ); 18 void exchange( unsigned char * , unsigned char * ); 19 void squeeze( unsigned char * , unsigned char * * ); 20 void output( const unsigned char * , const unsigned char * ) ; 21 void seek( unsigned char * , unsigned char * * ); 22 void add_1( unsigned char * , unsigned char * * ); 23 unsigned char * search ( unsigned char * , unsigned char * ); 24 25 int main( void ) 26 { 27 Number num ; 28 29 input( & num ); //輸入正整數 30 seek( num.digs , & num.p_high ); //求不重復數 31 output( num.p_high , num.digs ); //輸出 32 33 return 0; 34 } 35 36 /* 37 輸入數字到 p_n -> digs數組 ,p_high 記錄最高位位置 38 */ 39 void input( Number * p_n ) 40 { 41 int c ; 42 size_t max = SIZE(p_n -> digs) - 1u ; //預留進位位置,最多輸入數字位數 43 44 p_n -> p_high = p_n -> digs ; 45 46 while ( c = getchar() , BE_DIG( c ) && max -- > 0u ) 47 * p_n -> p_high ++ = c - '0' ; 48 49 if( p_n -> p_high == p_n -> digs ) //沒有輸入數字 50 exit( 1 ); 51 52 -- p_n -> p_high ; 53 reverse( p_n -> digs , p_n -> p_high ); //顛倒次序 54 squeeze( p_n -> digs , & p_n -> p_high ); //去掉開頭的0 55 } 56 57 /* 58 將從p_b指向的數字到p_e指向的數字逆序排列。123 -> 321 59 */ 60 void reverse( unsigned char * p_b , unsigned char * p_e ) 61 { 62 while ( p_e > p_b ) 63 exchange( p_b ++ , p_e -- ); 64 } 65 66 void exchange( unsigned char * p1 , unsigned char * p2 ) 67 { 68 unsigned char c = * p1 ; 69 * p1 = * p2 ; 70 * p2 = c ; 71 } 72 73 /* 74 去掉多余的高位0。00123 -> 123 75 */ 76 void squeeze( unsigned char * p_b , unsigned char * * pp_e ) 77 { 78 while ( * pp_e > p_b && * * pp_e == 0 ) 79 -- * pp_e ; 80 } 81 82 /* 83 * p_l指向最低位,* pp_h指向最高位 84 85 (A)首先最左位加1 (left) 2 2 1 1 => 3 2 1 1 86 由高到低查找重復位 3 2 (1) 1 87 新的區間為 (left)(1) 1 88 轉到(A) 3 2 (2) 1 89 (2) 1間沒有重復數字,循環結束 90 91 從 left-1 到 p_l 寫010101…… 92 */ 93 void seek( unsigned char * p_l , unsigned char * * pp_h ) 94 { 95 unsigned char * break_p = p_l ; 96 unsigned char * left ; 97 98 do 99 { 100 left = break_p ; 101 add_1( left , pp_h ); //[left , *pp_h]區間內最左位加1 102 } 103 while ( ( break_p = search ( left , * pp_h ) ) != NULL );//NULL:不能找到重復數字 104 105 //從 left-1 到 p_l 寫010101…… 106 while ( left > p_l ) 107 { 108 left[-1] = ! * left ; 109 left -- ; 110 } 111 } 112 113 /* 114 加1。處理了可能的進位,以及pp_h所指向的記錄最高位位置指針可能的改變 115 */ 116 void add_1( unsigned char * p_l , unsigned char * * pp_h ) 117 { 118 ++ * p_l ; //最低位加1 119 while ( p_l < * pp_h ) //進位處理 120 { 121 * ( p_l + 1 ) += * p_l / 10 ; 122 * p_l ++ %= 10 ; 123 } 124 if ( * * pp_h > 9 ) //最高位有進位 125 { 126 * ( * pp_h + 1 ) = * * pp_h / 10 ; 127 * (* pp_h) ++ %= 10 ; 128 } 129 } 130 131 /* 132 搜索p_h到p_l所指向數字中第一個重復數字的位置,如無則返回NULL 133 */ 134 unsigned char * search ( unsigned char * p_l , unsigned char * p_h ) 135 { 136 if ( p_h == p_l ) //一位數情形 137 return NULL ; 138 139 while ( p_l + 1 < p_h ) 140 { 141 if ( * p_h == p_h[-1] ) 142 return p_h - 1 ; 143 p_h -- ; 144 } 145 146 if ( * p_l == p_l[1] ) 147 return p_l ; 148 149 return NULL ; 150 } 151 152 /* 153 輸出p_h指向的數字到p_l指向的數字 154 */ 155 void output( const unsigned char * p_h , const unsigned char * p_l ) 156 { 157 while ( p_h > p_l ) 158 printf( "%u" , * p_h -- ); 159 160 printf( "%u" , * p_l ); 161 putchar('\n'); 162 }