【算法簡述】
馬拉車(Manacher)算法是在O(n)時間內解決尋找源字符串的最長回文子串S的問題的算法。
朴素算法情況下對於每一個S[i]都要左右遍歷其最大回文子串,所以時間復雜度是O(n2)
【算法原理】
充分利用之前求得的S【j】,為求S【i】服務。
預處理:在每個字符左右兩邊插入#將字符串變成奇數串
算法核心:
p[i] = mx > i ? min(p[2 * id - i], mx - i) : 1;
其中p[i]為S[i]的回文半徑,mx為i之前某一回文串右側最遠延伸到的位置,id為該回文串的中心點位置,很顯然從mx的對稱點到mx這一段是關於id對稱的。
如何利用之前求得的s[j]來求s[i]的回文串長度呢?
分情況討論
(1) 當 i在mx左側,p[j] < mx - i,即S[j]的回文子串的左端點不會超過mx的對稱點,那么很顯然S[i]的回文子串的右端點不會超過mx,所以此時取P[i] = p[j]
(2)當i在mx左側, p[j] > mx - i時,即S[j]的回文子串的左端點超過mx的對稱點,那么很顯然S[i]的回文子串的右端點至少要到mx,至於mx右側的,只能一個一個匹配以確定p[i]了。
(3)當i在mx右側,即i > mx,初始化p[i] = 1.這時候很顯然,mx和id是要更新的。因為此時回文串右側延伸的位置已經超過mx了。
【模板】
為了最后返回字符串的參數傳入方便,在字符串加了#的基礎上又加了$
//返回源字符串S的最長回文子串 string Manacher(string s){ //預處理源串 string t = "$#"; for(int i=0; i<s.size(); i++){ t+=s[i]; t+="#"; } //新建p數組,大小和t串一致,初始化為0 ,p[i]表示以t[i]為中心的回文串半徑 vector<int> p(t.size() , 0); //設定重要參數 mx(某回文串延伸到的最右邊下標),id(mx所屬回文串中心下標), //reCenter(結果最大回文串中心下標),reLen(最大長回文長度) int mx = 0, id = 0, reCenter = 0, reLen = 0; //遍歷t字符串 for(int i=1; i<t.size(); i++){ //核心算法 p[i] = mx > i ? min(mx - i , p[2*id - i]) : 1; //上面的語句只能確定i~mx的回文情況,至於mx之后的部分是否對稱,就只能老老實實去匹配了,匹配一個p[i]++ while(t[i + p[i]] == t[i - p[i]]) p[i]++; //當t[i]匹配的 右邊界超過mx時mx和id就更新 if(i+p[i] > mx){ mx = i+p[i]; id = i; } //更新結果數據 if(p[i] > reLen){ reLen = p[i]; reCenter = i; } } return s.substr((reCenter - reLen) / 2 , reLen - 1) ; }