前言
本人整理微軟等公司面試100題系列,包括原題整理,資源上傳,帖子維護,答案整理,勘誤,修正與優化工作,包括後續全新整理的80道,總計180道面試題,已有半年的時間了。
關於這180道面試題的一切詳情,請參見:橫空出世,席卷Csdn [評微軟等數據結構+算法面試180題]。
一直覺得,這180道題中的任何一題都值得自己反復思考,反復研究,不斷修正,不斷優化。之前的答案整理由於時間倉促,加之受最開始的認識局限,更兼水平有限,所以,這180道面試題的答案,有很多問題都值得進一步商榷與完善。
特此,想針對這180道面試題,再寫一個系列,叫做:程序員面試題狂想曲系列。如你所見,我一般確定要寫成一個系列的東西,一般都會永久寫下去的。
“他似風兒一般奔跑,很多人漸漸的停下來了,而只有他一直在飛,一直在飛....”
ok,本次系列以之前本人最初整理的微軟面試100題中的第26題、左旋轉字符串,為開篇,希望就此問題進行徹底而深入的闡述。然以下所有任何代碼僅僅只是全部測試正確了而已,還有很多的優化工作要做。歡迎任何人,不吝賜教。謝謝。
第一節、左旋轉字符串
題目描述:
定義字符串的左旋轉操作:把字符串前面的若干個字符移動到字符串的尾部。
如把字符串abcdef左旋轉2位得到字符串cdefab。
請實現字符串左旋轉的函數,要求對長度為n的字符串操作的時間復雜度為O(n),空間復雜度為O(1)。
編程之美上有這樣一個類似的問題,咱們先來看一下:
設計一個算法,把一個含有N個元素的數組循環右移K位,要求時間復雜度為O(N),
且只允許使用兩個附加變量。
分析:
我們先試驗簡單的辦法,可以每次將數組中的元素右移一位,循環K次。
abcd1234→4abcd123→34abcd12→234abcd1→1234abcd。
RightShift(int* arr, int N, int K)
{
while(K--)
{
int t = arr[N - 1];
for(int i = N - 1; i > 0; i --)
arr[i] = arr[i - 1];
arr[0] = t;
}
}
雖然這個算法可以實現數組的循環右移,但是算法復雜度為O(K * N),不符合題目的要求,要繼續探索。
假如數組為abcd1234,循環右移4位的話,我們希望到達的狀態是1234abcd。
不妨設K是一個非負的整數,當K為負整數的時候,右移K位,相當於左移(-K)位。
左移和右移在本質上是一樣的。
解法一:
大家開始可能會有這樣的潛在假設,K<N。事實上,很多時候也的確是這樣的。但嚴格來說,我們不能用這樣的“慣性思維”來思考問題。
尤其在編程的時候,全面地考慮問題是很重要的,K可能是一個遠大於N的整數,在這個時候,上面的解法是需要改進的。
仔細觀察循環右移的特點,不難發現:每個元素右移N位後都會回到自己的位置上。因此,如果K > N,右移K-N之後的數組序列跟右移K位的結果是一樣的。
進而可得出一條通用的規律:
右移K位之後的情形,跟右移K’= K % N位之後的情形一樣,如代碼清單2-34所示。
//代碼清單2-34
RightShift(int* arr, int N, int K)
{
K %= N;
while(K--)
{
int t = arr[N - 1];
for(int i = N - 1; i > 0; i --)
arr[i] = arr[i - 1];
arr[0] = t;
}
}
可見,增加考慮循環右移的特點之後,算法復雜度降為O(N^2),這跟K無關,與題目的要求又接近了一步。但時間復雜度還不夠低,接下來讓我們繼續挖掘循環右移前後,數組之間的關聯。
解法二:
假設原數組序列為abcd1234,要求變換成的數組序列為1234abcd,即循環右移了4位。比較之後,不難看出,其中有兩段的順序是不變的:1234和abcd,可把這兩段看成兩個整體。右移K位的過程就是把數組的兩部分交換一下。
變換的過程通過以下步驟完成:
逆序排列abcd:abcd1234 → dcba1234;
逆序排列1234:dcba1234 → dcba4321;
全部逆序:dcba4321 → 1234abcd。
偽代碼可以參考清單2-35。
//代碼清單2-35
Reverse(int* arr, int b, int e)
{
for(; b < e; b++, e--)
{
int temp = arr[e];
arr[e] = arr[b];
arr[b] = temp;
}
}
RightShift(int* arr, int N, int k)
{
K %= N;
Reverse(arr, 0, N – K - 1);
Reverse(arr, N - K, N - 1);
Reverse(arr, 0, N - 1);
}
這樣,我們就可以在線性時間內實現右移操作了。
稍微總結下:
編程之美上,
(限制書中思路的根本原因是,題目要求:“且只允許使用兩個附加變量”,去掉這個限制,思路便可如泉噴湧)
1、第一個想法 ,是一個字符一個字符的右移,所以,復雜度為O(N*K)
2、後來,它改進了,通過這條規律:右移K位之後的情形,跟右移K’= K % N位之後的情形一樣
復雜度為O(N^2)
3、直到最後,它才提出三次翻轉的算法,得到線性復雜度。
下面,你將看到,本章裡我們的做法是:
1、三次翻轉,直接線性
2、兩個指針逐步翻轉,線性
3、stl的rotate算法,線性
好的,現在,回到咱們的左旋轉字符串的問題中來,對於這個左旋轉字符串的問題,咱們可以如下這樣考慮:
1.1、思路一:
對於這個問題,咱們換一個角度,可以這麼做:
將一個字符串分成兩部分,X和Y兩個部分,在字符串上定義反轉的操作X^T,即把X的所有字符反轉(如,X="abc",那麼X^T="cba"),那麼我們可以得到下面的結論:(X^TY^T)^T=YX。顯然我們這就可以轉化為字符串的反轉的問題了。
不是麼?ok,就拿abcdef 這個例子來說(非常簡短的三句,請細看,一看就懂):
1、首先分為倆部分,X:abc,Y:def;
2、X->X^T,abc->cba, Y->Y^T,def->fed。
3、(X^TY^T)^T=YX,cbafed->defabc,即整個翻轉。
我想,這下,你應該了然了。
然後,代碼可以這麼寫(已測試正確):
view plaincopy to clipboardprint?
//Copyright@ 小橋流水 && July
//c代碼實現,已測試正確。
//http://www.smallbridge.co.cc/2011/03/13/100%E9%A2%98
//_21-%E5%B7%A6%E6%97%8B%E8%BD%AC%E5%AD%97%E7%AC%A6%E4%B8%B2.html
//July、updated,2011.04.17。
#include <stdio.h>
#include <string.h>
char * invert(char *start, char *end)
{
char tmp, *ptmp = start;
while (start != NULL && end != NULL && start < end)
{
tmp = *start;
*start = *end;
*end = tmp;
start ++;
end --;
}
return ptmp;
}
char *left(char *s, int pos) //pos為要旋轉的字符個數,或長度,下面主函數測試中,pos=3。
{
int len = strlen(s);
invert(s, s + (pos - 1)); //如上,X->X^T,即 abc->cba
invert(s + pos, s + (len - 1)); //如上,Y->Y^T,即 def->fed
invert(s, s + (len - 1)); //如上,整個翻轉,(X^TY^T)^T=YX,即 cbafed->defabc。
return s;
}
int main()
{
char s[] = "abcdefghij";
puts(left(s, 3));
return 0;
}
1.2、答案V0.3版中,第26題