Manacher演算法之個人愚見

2021-07-05 02:48:04 字數 4112 閱讀 9226

這個俗稱「馬拉車」的演算法的適用問題比較侷限,是manacher在解決尋找字串中最長回文時提出的一種時間複雜度為o(n)的演算法。廢話不多說,直接講演算法:

字串預處理

首先,在解決回文型別的演算法題目時,經常會很頭疼的問題就是最終解的回文中字元總數是奇數還是偶數,即abba與aba兩種型別,比如判斷乙個字串是不是回文就要區別對待這兩種情況。但在manacher演算法中首先針對這個問題提出了解決辦法,即在字串的開頭及每乙個字元後加上乙個固定的字元「#」(其實,一直有問題困擾我的就是如果字元值域涵蓋了整個字符集該如何解決?當然,不要在意這些細節)。

比如,字串:

12141214131(4為回文中心)

121441214131(44為回文中心)

在經過加處理後分別為:

#1#2#1#4#1#2#1#4#1#3#1#(4為回文中心)

#1#2#1#4#4#1#2#1#4#1#3#1#(#為回文中心)

可以看出,經過處理後的奇偶不同的問題就解決了,剩下的問題就是如何在處理後的字串中找到最長回文。

尋找最長回文

就如何尋找最長回文這個問題,首先,先介紹幾個變數:

s[i] : 當前字元。

p[i] : 在以當前字元s[i] 為回文中心的回文半徑長度。

final_index : 簡稱(fi),這個變數代表當前找到的所有回文中最右端的乙個字元索引。

center_index : 簡稱(ci)這個變數表示的就是能到達fi索引的回文的中心索引。

以上變數符合等式:fi = ci + p[ci],即:當前能達到的最右端的索引fi等於以fi為結尾的回文的中心s[ci]的索引ci加上其回文半徑p[ci]。沒看懂?上例子: #1#2#1#4#1#2#1#4#1#3#1#,當前考慮s[7] ,也就是字元4,以該字元為中心的回文是#1#2#1#4#1#2#1#,是當前能達到最右端的索引的回文(注意最右端,並不是最長),所以p[7] = 7(不含中心本身),此時fi = ci + p[ci] = 7 + p[7]=14,此時將ci置為 7 。當計算到s[11]時,此時回文達到的最右端為18 > 14,此時需要更新ci = 11,fi = 18。如果還沒明白就看圖吧。

計算完p[7]後的ci,fi位置。

計算完p[11]後的ci,fi位置。

明白這些變數的含義與等式關係後,接下來就是演算法的最核心的部分,如何利用已經計算過的p[0]……p[ci]...來求出p[i];但是得到p[i]的方法要根據不同的情況分開來考慮,i 與 fi的大小是分情況討論的條件。

(一)首先看i > fi的情況

假設我們當前要計算下圖中綠色3字元為中心的回文半徑即p[19]。而根據之前的計算fi = 18,ci = 11,i=19 >fi,說明目前還沒有乙個回文包括s[19],所以計算

p[19]

只能靠簡單前後遍歷得到。

(二)再來看i < fi 的情況,這種情況下還有三種不同子情況,下面分開來說:

(1)假設現在要計算p[9],此時9 < fi,說明s[9]已經存在於乙個回文中,而這個回文我們記錄下了即:ci = 7代表中心,p[ci] = 7 代表半徑的回文中(圖中藍色雙箭頭範圍),根據回文的對稱性,p[9] 可以參見 p[ci –(9-ci)] 即p[5] 的情況,因為 5,9以7為中心對稱,雖然可以參見p[5] ,但是不能斷定p[9] 等於 p[5],具體情況還要看p[5],p[5] = 1可以看出,以

s[5]

為中心的回文(圖中橙色雙箭頭範圍)完全包含在以

s[7]

為中心的回文中,且沒有到達

s[7]

為中心的回文的左邊界,此時可以斷定p[9] = p[5];那麼有乙個疑問,有沒有可能p[9]>p[5]或p[9]

以s[5]

為中心的回文完全包含在以

s[7]

為中心的回文中,且沒有到達

s[7]

為中心的回文的左邊界。重要的事情說三遍:以

s[5]

為中心的回文完全包含在以

s[7]

為中心的回文中,且沒有到達

s[7]

為中心的回文的左邊界。

(2)假設現在要計算p[11],同樣的 11 < fi,s[11]存在於s[ci = 7]為中心,p[ci] = 7 代表半徑的回文中,根據回文的對稱性,p[11] 要參看p[3],而p[3] = 3,此時出現了以

s[3]

為中心的回文(下圖橙色雙箭頭範圍)的左邊界跟以

s[ci=4]

為中心的回文(下圖藍色雙箭頭)的左邊界重合的情況,根據對稱性,s[11]的回文右邊界一定能達到以s[ci=4]為中心的回文(下圖綠色雙箭頭範圍)的右邊界即fi。由第一種情況可以推知

p[11]

是不小於

p[3]

的,但有沒有可能大於p[3]呢?如下圖,可以看出來,根據第一種情況首先可以確定以s[11]為中心的回文是不小於綠色雙箭頭所指範圍的,實際情況是綠色箭頭範圍的兩邊延伸也有屬於s[11]為中心的回文即黃色範圍,因此在計算

p[11]

時,還需要遍歷一下綠色範圍的前後。

(3)再來說第三種情況,我們在第二種情況下修改一下例子來討論第三種情況,假設例子中字串的前面還有若干字串有n個。同樣的假設現在要計算p[n+11],n+11 < n+fi,s[n+11]存在於s[ci = n+7]為中心,p[ci] = 7 代表半徑的回文中,根據回文的對稱性,p[n+11] 要參看p[n+3],而此時我們假設p[n+3] = 5。可以看出此時橙色回文的左邊界已經超過了藍色回文的左邊界,此時的p[n+11]又要如何計算呢?其實這種情況的計算方法和第二種情況相同,我們首先看圖中紅色範圍中的回文,這部分回文既在橙色回文中也在藍色回文中,且根據對稱性可知,圖中綠色回文跟紅色回文是相同的,因為紅色回文左邊界跟藍色回文的左邊界重合,所以綠色回文的右邊界跟藍色回文的右邊界重合,所以可以肯定的是p[n+11]>=n+fi –(n+11)

,其中n+fi-(n+11)

就是計算

s[n+11]

到s[n+fi]

的距離。而在綠色回文的延伸部分中是否也有回文就要看前後遍歷的結果了。

其實第2 、3兩種子情況可以合起來算作一種即:當橙色回文的左邊界小於等於藍色回文左邊界的時候,所求綠色回文的右邊界就一定能達到藍色回文的右邊界,至於有沒有延伸就要看前後遍歷的結果了。

**實現

const int max_size=10000000+10;  

char str[max_size];//原字串

char tmp[max_size<<1];//預處理後的字串

int len[max_size<<1];

//預處理原始串

int predo(char *str)

tmp[2*len+1]='#';

tmp[2*len+2]='@';//字串結尾加乙個字元,防止越界

return 2*len+1;//返回轉換字串的長度,不包括開頭結尾加上的字元

} //manacher演算法計算過程

int manacher(char *tmp,int len)

ret=max(ret,len[i]);

} return ret;

}

演算法之Manacher

計算乙個最長回文子串行的樸素演算法的時間複雜度為o n 2 而該演算法使時間複雜度降至o n 該演算法利用的原理為 當乙個小回文序列在乙個大回文序列中時,其小回文序列在大回文序列的對稱位置的序列與原小回文序列一致。利用這個原理,可以減少單一回文序列的計算量。另外,為使 更加簡練,可以對初始字串進行處...

個人愚見 React 和 Vue 區別

一.相似之處 它們都是前端優秀的ui庫 使用 virtual dom快速渲染 提供了響應式 reactive 和元件化 composable 的檢視元件。都支援服務端渲染 將注意力集中保持在核心庫,而將其他功能如路由和全域性狀態管理交給相關的庫。二.不同之處 效能方面react 元件的狀態有變化時,...

Manacher演算法總結

所謂回文串,簡單來說就是正著讀和反著讀都是一樣的字串,比如abba,noon等等,乙個字串的最長回文子串即為這個字串的子串中,是回文串的最長的那個。下面介紹manacher演算法的原理與步驟。首先,manacher演算法提供了一種巧妙地辦法,將長度為奇數的回文串和長度為偶數的回文串一起考慮,具體做法...