關於這個問題,已經有很多牛人都討論過了,而且我的整理也是來自於July大神的這篇部落格
http://blog.csdn.net/v_july_v/article/details/6322882 。
我的這篇部落格也算是站在巨人的肩膀上吧,不能算盜版吧!寫這篇博文有兩點原因,一是因為原博文看起來有點繁雜,如果是沒有耐心的人估計難以堅持看完。事實上我也是結合自己的想法跳著看完了,沒有看代碼,這是我自己一貫的思路。代碼是實現的細節,只要演算法的思路清晰了,那麼代碼的實現是沒有問題的。所以秉承這個思路,我的這篇博文不會貼上代碼,從頭至尾梳理演算法的思路,不過不保證以後更新的時候不會加上代碼。另外一個原因便是該問題確實比較值得思考,不同的思路,不同的做法,很有思考的意義。基於上面兩點,寫下了這篇博文。
問題定義
一個長度為n的字串,我們要迴圈左移m位。例如abcde,迴圈左移2位的結果就是cdeab。要求在O(n)的時間複雜度和O(1)的空間複雜度內解決該問題。
對於一個長度為n的串,如果我們左移n位,結果就是原串,因此我們的m = m%n,對於這一點是沒有問題的。其次如果我們面對的是右移m`位(m`<n)怎麼辦,那麼我們對應的就是左移n-m`位,這個也很容易知道為什麼是對的,在此便不加解釋。那麼下面我們就來全神貫注的思考如何解決一個字串左移問題。
使用字串翻轉的辦法
很明顯我們可以知道如果將abcde左移2位得到的結果便是cdeab,其實我們可以看到就是相當於將前2位直接搬到了原串的後面。
於是我們很容易想到先將abcde整體翻轉得到edcba,然後將後面的m位單獨翻轉,得到edcab,再將前面的(n-m)位進行翻轉得到cdeab。於是很清晰的得到了翻轉解決該問題的辦法。
對於一個給定的串進行反轉的程式,我們姑且認為是O(1)的空間複雜度,那麼整個辦法的時間複雜度就是嚴格的O(2n),我想這個辦法已經很不錯了吧。
遞迴解決該問題
同樣舉abcde左移2位的這個例子,結果就是cdeab,我們發現只是將ab作為整體移到了串的末尾。因此我們嘗試直接先將前m位直接移動到目標位置得到decab,我們發現前面的3位便是dec,只需要左移兩位便是正確的結果。因此不難發現該問題可以用遞迴解決。
另外我們注意,當m>(n-m)的時候,例如abcde左移3位,我們先移動前面2位到達目標位置得到decab,顯然我們只需要將後面的三位左移1位就行了。因此我們知道在m>(n-m)的時候,我們將前面的(n-m)位同後面的(n-m)位交換,然後將串的後m位左移(m-(n-m))位即可。
事實上,上面兩個情況是一樣的,都是將串的前m位同串末尾的m位進行對應的交換,只不過因為實際情況的不同分為上面的兩個走向。
遞迴的方法應該說達到了嚴格的O(n)的複雜度,但是我們知道,遞迴是要系統自動維護棧的,因此對於這種解法所消耗的資源我們存保留意見。
利用數學解決該問題
其實對於這道題,最初一看的想法就是將當前位依次替換左移m位對應的那個位,然後依次替換。後來發現有的情況一次迴圈替換就能全部完成整個串的左移,而有的情況下會出現多個迴圈鏈,一時只得到規律,不能想到很好的證明辦法,只怪以前初等數論沒有好好學啊!
我們發現對於長度為n的串,左移m位,會形成gcd(n, m)個鏈,這裡的gcd就是大家熟知的最大公約數,每個鏈的長度顯然就是n/(gcd(n, m))。我們如何來證明這個問題呢?我們令i+j*m 和 i+k*m正好走了一圈又指向同一個元素,那麼便是(i+j*m)%n == (i+k*m)%n,因此根據同餘的性質我們知道n | (k-j)*m,而n
= n' * gcd(n, m), m = m' * gcd(n, m)。所以我們得到n' | (k-j) * m', 由於n' 和 m' 互素,因此 n' | (k - j),因此我們知道最小的k-j = n' = n/gcd(n, m),也就是說一個迴圈鏈裡頭有n/gcd(n, m)個元素,因此總共有gcd(n, m)個迴圈鏈,於是得證。因此我們只需要取前gcd(n, m)個元素,每個走一條鏈進行迴圈的替換就行了。其實這個演算法是最優的,真正的O(N)的時間複雜度,而且沒有額外的開銷。
後記
你可能發現了,我在這裡並沒有提及前面串連部落格中提到的雙指標的做法,因為那種做法始終逃不過最壞情況是O(n^2)的悲劇命運。就舉個例子說一個長度為2k-1的串,左移k位,那麼按照那裡的思路,我們需要將後面的(k-1)位逐個向前移動k位,顯然時間複雜度就是k*(k-1),這裡的k就是n/2,所以近似複雜度就是O(n^2)的。我們看為什麼會這樣子,因為那個方法最後在剩下的字元數小於m的時候,要逐個的移動每個字元m次,這樣一下子提高了演算法的複雜度。
其實上面提到的三種思路已經很優了,解決一道題能想到三種辦法已經很好了,在這裡對July表示一下仰慕。我在這裡只是對原部落格中的內容進行一下概括性的綜述,如果想要看代碼和更詳細內容的讀者可以前往開頭的連結部落格進行學習。
另外若是讀者發現該文中有問題,希望牛人不吝賜教,我當認真傾聽教誨。
最後祝各位元旦快樂,新年新氣象。