在http://www.cnblogs.com/bestDavid/p/Stringeliminate.html看到了一個有趣的小題:
給定一個字符串,僅由a,b,c 3種小寫字母組成。當出現連續兩個不同的字母時,你可以用另外一個字母替換它。
如有ab或ba連續出現,你把它們替換為字母c;
有ac或ca連續出現時,你可以把它們替換為字母b;
有bc或cb連續出現時,你可以把它們替換為字母a。
你可以不斷反復按照這個規則進行替換,你的目標是使得最終結果所得到的字符串盡可能短,求最終結果的最短長度。
輸入:字符串。長度不超過200,僅由abc三種小寫字母組成。
輸出:按照上述規則不斷消除替換,所得到的字符串最短的長度。
例如:
輸入cab,輸出2。因為我們可以把它變為bb或者變為cc。
輸入bcab,輸出1。盡管我們可以把它變為aab -> ac -> b,也可以把它變為bbb,但因為前者長度更短,所以輸出1。
這個題目原博文中是用“數學”的方法給出解答的,但其推理證明過程(包括引文給出的證明過程)並不嚴謹,結論依據不足。所以這里用程序員的思考方法給出解答。
程序員的方法無非就是老老實實地進行替換。對每一種可以替換的情形都進行替換。例如對bcab,可以替換為aab,bbb和bcc。題中提到“可以不斷反復按照這個規則進行替換”,所以上面的各個情況又可以進一步
aab:
aab->ac->b 長度為1.
bbb:
無不同字母,無法進一步替換。長度為3
bcc:
bcc->ac->b 長度為1
綜上,對bcab進行不斷變換,所得到的字符串的最短長度為1。
因此,很容易給出代碼總體結構。
1 #include <stdio.h> 2 3 #define MAX 200 4 5 typedef char String[ MAX + 1 ]; 6 7 #define N(x) N_(x) 8 #define N_(x) #x 9 10 void input( char * ); 11 unsigned min_len( char * ); 12 13 int main( void ) 14 { 15 String str; 16 17 input( str ); 18 19 printf("替換最終結果的最短長度為%u。\n" , min_len( str ) ); 20 21 return 0; 22 } 23 24 unsigned min_len( char *s ) 25 { 26 27 } 28 29 void input( char *s ) 30 { 31 puts("輸入字符串"); 32 scanf("%"N(MAX)"[abc]s",s); 33 puts("要確定長度的字符串為:"); 34 puts( s ); 35 }
其中宏 MAX 為字符串初始最大長度,為了給字符串結尾的\0留出空間,存儲字符串的字符數組的最大尺寸為 MAX + 1 。由於這種類型在整個代碼中頻繁用到,所以用 typedef 為其取了一個統一的名字。
函數input()輸入字符串由於字符串由a、b、c三種字母組成,所以禁止輸入其他字符這就是 scanf("%"N(MAX)"[abc]s",s); 中[abc]的目的。又考慮到防止數組輸入越界,因此為輸入增加了寬度限制——N(MAX),宏展開后的結果為"200"。所以"%"N(MAX)"[abc]s"就是"%200[abc]s"。
下面考慮min_len( char *s )函數。基本思路就是先求出s的初始長度
int len = strlen( s );
然后從頭至尾檢查是否有相鄰的相異字符,如有則替換:
reduce( newstr , s , i1 );
這樣將得到一個新的字符串newstr。設新字符串的長度為
int newlen ;
新字符串最終長度顯然仍然可以用min_len()求得:
newlen = min_len( newstr );
如果newlen小於len,則將len記為newlen的值。
if ( newlen < len ) { len = newlen ; }
這樣返回最后的len就是變換后的最短長度。min_len函數完整代碼如下:
1 unsigned min_len( char *s ) 2 { 3 int len = strlen( s ); 4 int i = 0 ; 5 while ( s[i] != '\0' && s[i+1] != '\0' ) 6 { 7 if ( s[i] != s[i+1] ) 8 { 9 String newstr ; 10 unsigned newlen ; 11 12 reduce( newstr , s , i ); 13 newlen = min_len( newstr ); 14 if ( newlen < len ) 15 { 16 len = newlen ; 17 } 18 } 19 i ++ ; 20 } 21 return len; 22 }
再來看一下reduce()函數。完成三件事:
- 將ss[i]前面的字符——一共i個,copy到st中:strncpy( st , ss , i );
- 根據ss[i],ss[i+1]求出替換字符,並寫到st[i]中。
- 將ss中ss[i+1]以后的部分copy到st中從st[i+1]開始的位置。
至此,問題解決,全部代碼完成。下面是完整的代碼。
1 #include <stdio.h> 2 #include <string.h> 3 4 #define MAX 200 5 6 typedef char String[ MAX + 1 ]; 7 8 #define N(x) N_(x) 9 #define N_(x) #x 10 11 void input( char * ); 12 unsigned min_len( char * ); 13 void reduce( char * , char * , unsigned ); 14 char turn ( char , char ); 15 16 int main( void ) 17 { 18 String str; 19 20 input( str ); 21 22 printf("替換最終結果的最短長度為%u。\n" , min_len( str ) ); 23 24 return 0; 25 } 26 27 char turn ( char c1 , char c2 ) 28 { 29 return 'a' + 'b' + 'c' - c1 - c2 ; 30 } 31 32 void reduce( char * st , char * ss , unsigned i ) 33 { 34 strncpy( st , ss , i ); 35 st[i] = turn ( ss[i] , ss[i+1] ); 36 strcpy( st + i + 1 , ss + i + 2 ); 37 } 38 39 unsigned min_len( char *s ) 40 { 41 int len = strlen( s ); 42 int i = 0 ; 43 while ( s[i] != '\0' && s[i+1] != '\0' ) 44 { 45 if ( s[i] != s[i+1] ) 46 { 47 String newstr ; 48 unsigned newlen ; 49 50 reduce( newstr , s , i ); 51 newlen = min_len( newstr ); 52 53 if ( newlen < len ) 54 len = newlen ; 55 } 56 i ++ ; 57 } 58 return len; 59 } 60 61 void input( char *s ) 62 { 63 puts("輸入字符串"); 64 scanf("%"N(MAX)"[abc]s",s); 65 66 puts("要確定長度的字符串為:"); 67 puts( s ); 68 }
補記:
萬倉一黍 網友就這個問題給出了一個徹底的數學證明 《算法:字符串消除問題的數學證明》 ,根據其結論可以得到更簡潔的算法。
另,曉得飛天千秋雪 網友應邀也給出了一個漂亮的證明——《字符串壓縮問題》。