Manacher算法,又叫“馬拉車”算法,可以在時間復雜度為O(n)的情況下求解一個字符串的最長回文子串長度的問題。
一、回文子串的一般解法
二、Manacher算法中的基礎概念
1、回文半徑數組radius

2、最右回文右邊界R
一個位置最右回文右邊界指的是這個位置及之前的位置的回文子串,所到達的最右邊的地方。比如對於字符串#a#c#b#b#c#b#d#s#,求它的每個位置的過程如下:
最右回文右邊界R過程
最開始的時候R=-1,到p=0的位置,回文就是其本身,最右回文右邊界R=0;p=1時,有回文串#a#,R=2;p=2時,R=2;P=3時,R=6;p=4時,最右回文右邊界還是p=3時的右邊界,R=6,依次類推。
3、最右回文右邊界的對稱中心C
就是上面提到的最右回文右邊界的中心點C,如下圖,p=4時,R=6,C=3

三、Manacher算法的流程
首先大的方面分為兩種情況:
第一種情況:下一個要移動的位置在最右回文右邊界R的右邊。
比如在最開始時,R=-1,p的下一個移動的位置為p=0,p=0在R=-1的右邊;p=0時,此時的R=0,p的下一個移動位置為p=1,也在R=0的右邊。
在這種情況下,采用普遍的解法,將移動的位置為對稱中心,向兩邊擴,同時更新回文半徑數組,最右回文右邊界R和最右回文右邊界的對稱中心C。
第二種情況:下一個要移動的位置就是最右回文右邊界R或是在R的左邊
在這種情況下又分為三種:
1、下一個要移動的位置p1不在最右回文右邊界R右邊,且cL<pL。
p2是p1以C為對稱中心的對稱點;
pL是以p2為對稱中心的回文子串的左邊界;
cL是以C為對稱中心的回文子串的左邊界。
這種情況下p1的回文半徑就是p2的回文半徑radius[p2]。

2、下一個要移動的位置票p1不在最右回文右邊界R的右邊,且cL>pL。
p2是p1以C為對稱中心的對稱點;
pL是以p2為對稱中心的回文子串的左邊界;
cL是以C為對稱中心的回文子串的左邊界。
這種情況下p1的回文半徑就是p1到R的距離R-p1+1。

3、下一個要移動的位置票p1不在最右回文右邊界R的右邊,且cL=pL;
p2是p1以C為對稱中心的對稱點;
pL是以p2為對稱中心的回文子串的左邊界;
cL是以C為對稱中心的回文子串的左邊界。
這種情況下p1的回文半徑就還要繼續往外擴,但是只需要從R之后往外擴就可以了,擴了之后更新R和C。

四、Manacher時間復雜度分析
從上面的分析中,可以看出,第二種情況的1,2的求某個位置的回文半徑的時間復雜度是O(1),對於第一種情況和第二種情況的3,R是不斷的向外擴的,不會往回退,而且尋找回文半徑時,R之內的位置是不是進行判斷的,所以對整個字符串而且,R的移動是從字符串的起點移動到終點,時間復雜度是O(n),所以整個manacher的時間復雜度是O(n)。
五、Manacher的代碼實現
#include<iostream> #include<vector> using namespace std; int Manacher(string str){ int str_len = str.size(); vector<char> tmp; tmp.push_back('$'); int len[2*str_len]; for(int i = 1;i < str_len;i++){ tmp.push_back('#'); tmp.push_back(str[i]); } tmp.push_back('#'); for(char i : tmp) cout << i; cout << endl; int mx = 0; int max_len = -1; int mid = 0, pos = 0; for(int i = 1; tmp[i]; i++) { if(i < mx) len[i] = min(len[2*mid-i],mx-i); else len[i] = 1; while(tmp[i-len[i]] == tmp[i+len[i]]) len[i]++; if(len[i]+i > mx){ mx = len[i]+i; mid = i; } if(max_len <= len[i]-1) { max_len = len[i]-1; pos = i; } // maxlen = max(maxlen,len[i]-1); } for(int i=pos-max_len; i<pos+max_len; ++i) { if (tmp[i] != '#') cout << tmp[i]; } cout << endl; return max_len; } int main(){ string str; cin >> str; cout << Manacher(str); return 0; }