Manacher算法是查找一个字符串的最长回文子串的线性算法。
把长度为奇数的回文串和长度为偶数的回文串一起考虑,具体做法是,在原字符串的每个相邻两个字符中间插入一个分隔符,同时在首尾也要添加一个分隔符,分隔符的要求是不在原串中出现,一般情况下可以用#号。为了边界问题,我们在字符串起始位置加入两个不同的字符。
用一个辅助数组hw[i]表示以字符a[i]为中心的最长回文字串的最右字符到a[i]的长度
hw数组有一个性质,那就是hw[i]-1就是该回文子串在原字符串S中的长度。
**证明:**首先在转换得到的字符串a中,所有的回文字串的长度都为奇数,那么对于以T[i]为中心的最长回文字串,其长度就为2*hw[i]-1,T中所有的回文子串,其中分隔符的数量一定比其他字符的数量多1(结束的位置一定是‘#’),也就是有hw[i]个分隔符,剩下hw[i]-1个字符来自原字符串,所以该回文串在原字符串中的长度就为hw[i]-1。
设置一个maxr表示已经触及的最右字符位置,mid表示以maxr为终点的回文串的对称字符的位置
从左到右计算hw[i],
如果i < maxr
那么我们求出i关于mid的对称点j。由于一个回文串反过来还是一个回文串,所以以i为中心的回文串的长度至少和以j为中心的回文串一样。但是以i为中心的回文串可能会延伸到maxr之外,而大于maxr的部分我们还没有进行匹配,所以要从maxr+1位置开始一个一个进行匹配,直到发生失配,从而更新maxr和对应的mid以及hw[i]。
如果i > maxr
对于中点为i的回文串还一点都没有匹配,这个时候,就只能老老实实地一个一个匹配了,匹配完成后要更新maxr的位置和对应的mid以及hw[i]。
#include <iostream>
#include <string>
using namespace std;
//复杂度为O(n)
string a;
int hw[22000005];
void init()
{
string res;
int len = a.length();
res += '@'; //最开始加与末尾不对称的字符
for (int i = 0; i < len; i++)
{
res += '#'; //加'#'和原来的字符
res += a[i];
}
res += '#';
res += '$'; //与开始的字符不对称
a = res;
}
int manacher()
{
int ans = 0;
int maxr = -1,mid;
int len = a.length();
for (int i = 1; i < len - 1; i++)
{
if( i < maxr ) hw[i] = min(hw[(mid<<1)-i],maxr-i);
/*如果i < maxr,
1.如果最小值是hw[(mid<<1)-i],那么不会进入while中,说明以i为中心的回文串被mid的回文串完全包含
2.如果最小值是maxr-i,说明以i为中心的回文串超过了maxr,这时以maxr为起点更新
*/
else hw[i] = 1; //否则默认初始值为1
while( a[i-hw[i]] == a[i+hw[i]] ) hw[i] ++;
if( hw[i] + i > maxr ) //更新maxr和mid
{
maxr = hw[i] + i;
mid = i;
}
ans = max(ans,hw[i] - 1);
}
return ans;
}
int main()
{
cin >> a;
init();
cout << manacher() << endl;
return 0;
}