百度2013校園招聘筆試題 算法與程序設計 第4題:
字符串左移,void *pszStringRotate(char *pszString, int nCharsRotate),比如ABCDEFG,移3位變DEFGABC,要求空間複雜度O(1),時間複雜度O(n)。
字符串位移,再簡單不過了,方法也很多. 有樸素算法(逐位移動,移動nCharsRotate輪)時間複雜度O(n^2),或者另開一等長新串,計算好原串中各個字符偏移之後在新串中的映射位置,時間複雜度O(n),空間複雜度O(n)。而題目要求空間複雜度O(1),時間複雜度O(n),就必須開闢新思路,想想時空複雜度更優的新算法了。
我的算法思路如下:
1.設下標索引cnt爲當前在原串操作的下標位置,初始爲0。
2.進行strlen(pszString)-1輪操作(i表示當前是第幾次操作):
1.判斷cnt和cnt循環左移i*nCharsRotate個長度位置是否是同一位置,若是,跳轉至3。
2.交換cnt位置和cnt循環左移i*nCharsRotate個長度位置上的字符。
3.cnt循環右移一個長度,跳轉至2。
手工模擬一個例子:ABCDEFG,左移3位
初始:ABCDEFG ,cnt = 0
交換A和(cnt+7-3%7)%7位置的字符E,得:EBCDAFG
此時cnt依然等於0,但左移長度變爲2*3 = 6
交換E和(cnt+7-6%7)%7位置的字符B,得:BECDAFG
同理,第3步,B與F交換,得FECDABG
第4步,F與C交換,得CEFDABG
第5步,C與G交換,得GEFDABC
第6步,G與D交換,得DEFGABC
算法完成,得到目標串:DEFGABC
但此例比較特殊,由於(偏移長度%串長度)和串長度互素,所以任意兩個交換的位置不會相等,所以cnt得不到更新,一直爲0,下面看一個一般的例子:ABCDEFGH,左移4位
初始:ABCDEFGH,cnt = 0
交換A和(cnt+8-4%8)%8位置的字符E,得:EBCDAFGH
進行第2輪操作,發現cnt和(cnt+8-8%8)%8相等,故cnt循環右移,變爲1.
交換B(cnt位置)和(cnt+8-12%8)%8位置的字符F,得:EFCDABGH
同理,第4步,C與G交換,得:EFGDABCH
第5步,位置相同,cnt循環右移
第6步,D與H交換,得:EFGHABCD
第7步,位置相同,cnt循環右移
算法完成,得到目標串:EFGHABCD
C++實現代碼如下:
算法正確性簡單證明:
第1次操作,cnt位置上的字符需要循環左移nCharsRotate個長度以達到正確位置,如果這兩個位置是相等的,說明cnt位置上的字符不需要操作了,循環右移cnt(由於從0開始,可以保證其不重複)。如果這兩個位置不相等,交換這兩個位置的字符,此時cnt位置上的字符是新字符,這個新字符被相對循環右移了nCharsRotate個長度,而要使其達到正確位置,則需要循環左移2*nCharsRotate個長度。可以歸納出第i輪操作需要循環左移i*nCharsRotate個長度。操作完第strlen(pszString)-1輪操作即可終止,因爲此輪操作只有兩種可能:1.與自身交換,則最後一個字符也與自身交換,不需要額外操作;2.與最後一個字符交換,則最後一個字符在此輪操作已經被移動到了正確的位置,不需要額外操作。綜上,不需要第strlen(pszString)次操作。
算法應該還可以進一步優化,比如先判斷是否互素,再進行對應操作,終止輪數也相應減少,會少一些無謂的判斷,現在的strlen(pszString)-1輪只是上界,真正的交換次數與上述兩個量的最大公約數有關。
此外,本題還有利用串翻轉性質的高效算法,本人才疏學淺,就不再詳述了。
字符串左移,void *pszStringRotate(char *pszString, int nCharsRotate),比如ABCDEFG,移3位變DEFGABC,要求空間複雜度O(1),時間複雜度O(n)。
字符串位移,再簡單不過了,方法也很多. 有樸素算法(逐位移動,移動nCharsRotate輪)時間複雜度O(n^2),或者另開一等長新串,計算好原串中各個字符偏移之後在新串中的映射位置,時間複雜度O(n),空間複雜度O(n)。而題目要求空間複雜度O(1),時間複雜度O(n),就必須開闢新思路,想想時空複雜度更優的新算法了。
我的算法思路如下:
1.設下標索引cnt爲當前在原串操作的下標位置,初始爲0。
2.進行strlen(pszString)-1輪操作(i表示當前是第幾次操作):
1.判斷cnt和cnt循環左移i*nCharsRotate個長度位置是否是同一位置,若是,跳轉至3。
2.交換cnt位置和cnt循環左移i*nCharsRotate個長度位置上的字符。
3.cnt循環右移一個長度,跳轉至2。
手工模擬一個例子:ABCDEFG,左移3位
初始:ABCDEFG ,cnt = 0
交換A和(cnt+7-3%7)%7位置的字符E,得:EBCDAFG
此時cnt依然等於0,但左移長度變爲2*3 = 6
交換E和(cnt+7-6%7)%7位置的字符B,得:BECDAFG
同理,第3步,B與F交換,得FECDABG
第4步,F與C交換,得CEFDABG
第5步,C與G交換,得GEFDABC
第6步,G與D交換,得DEFGABC
算法完成,得到目標串:DEFGABC
但此例比較特殊,由於(偏移長度%串長度)和串長度互素,所以任意兩個交換的位置不會相等,所以cnt得不到更新,一直爲0,下面看一個一般的例子:ABCDEFGH,左移4位
初始:ABCDEFGH,cnt = 0
交換A和(cnt+8-4%8)%8位置的字符E,得:EBCDAFGH
進行第2輪操作,發現cnt和(cnt+8-8%8)%8相等,故cnt循環右移,變爲1.
交換B(cnt位置)和(cnt+8-12%8)%8位置的字符F,得:EFCDABGH
同理,第4步,C與G交換,得:EFGDABCH
第5步,位置相同,cnt循環右移
第6步,D與H交換,得:EFGHABCD
第7步,位置相同,cnt循環右移
算法完成,得到目標串:EFGHABCD
C++實現代碼如下:
#include <cstdio>
#include <cstring>
using namespace std;
char str[1010];
int n;
void swap(char *c1, char *c2) {
char tmp = *c1;
*c1 = *c2;
*c2 = tmp;
}
void pszStringRotate(char *pszString, int nCharsRotate) {
int len = strlen(pszString);
int cnt = 0; // Current operate position on pszString
int shift = nCharsRotate % len; // Current shift length
for (int i = 1; i <= len - 1; i ++) { // Operation times upper bound : len - 1
if (cnt == (cnt + len - shift) % len) { // Collision found
cnt ++; // Update current operate position
cnt %= len;
}
swap(&pszString[cnt], &pszString[(cnt + len - shift) % len]); // Do operate
shift += nCharsRotate; // Update shift length
shift %= len;
}
}
int main () {
while (~scanf("%s%d", str, &n)) {
pszStringRotate(str, n);
printf("%s\n", str);
}
return 0;
}
算法正確性簡單證明:
第1次操作,cnt位置上的字符需要循環左移nCharsRotate個長度以達到正確位置,如果這兩個位置是相等的,說明cnt位置上的字符不需要操作了,循環右移cnt(由於從0開始,可以保證其不重複)。如果這兩個位置不相等,交換這兩個位置的字符,此時cnt位置上的字符是新字符,這個新字符被相對循環右移了nCharsRotate個長度,而要使其達到正確位置,則需要循環左移2*nCharsRotate個長度。可以歸納出第i輪操作需要循環左移i*nCharsRotate個長度。操作完第strlen(pszString)-1輪操作即可終止,因爲此輪操作只有兩種可能:1.與自身交換,則最後一個字符也與自身交換,不需要額外操作;2.與最後一個字符交換,則最後一個字符在此輪操作已經被移動到了正確的位置,不需要額外操作。綜上,不需要第strlen(pszString)次操作。
算法應該還可以進一步優化,比如先判斷是否互素,再進行對應操作,終止輪數也相應減少,會少一些無謂的判斷,現在的strlen(pszString)-1輪只是上界,真正的交換次數與上述兩個量的最大公約數有關。
此外,本題還有利用串翻轉性質的高效算法,本人才疏學淺,就不再詳述了。