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; }