【解题思路】求一个字符串的最长回文子串,如果暴力,每一个字符向他两端展开看是否相同,遍历整个数组便可以找到最长回文子串,但麻烦在还得分长度奇偶来考虑,比如”noon“,“level”是两种情况,并且时间复杂度很高,为O(n*n)。
Manacher‘s Algorithm将求最长回文子串的时间复杂度提高到了线性,这是非常了不起的。具体怎么操作我们一起来看!
第一步是预处理,给每一个字符前后两端都加上#
比如 aba-->#a#b#a#
noon-->#n#o#o#n#
这样做的好处在于长度为奇偶的串都变成了一种情况,求每一个字符向两端展开为回文半径,这个半径减去1就是本来回文串的长度,比如我们看#a#b#a#这个b在这个串里的最长半径为4,而aba的长度为3,符合。
再看#n#o#o#n#最中间的#半径为5,而noon的长度为4,也符合。
开一个p数组用来存放半径长度。
这样就解决了奇偶不同的问题,现在再看manacher的核心部分。
先上这张经典图 :
mx为能延伸到最右端的位置,id是他的中点位置。
p[I]=mx-I?min(p[2*id-I],mx-I):1;
当mx>i时,p[i]就为p[2*id-i]和mx-i中小的那个,为什么是这样呢?看图,2*id-i也就是i关于id的对称点,要理解它为什么是取小的那个,1.如果mx-i大于p[2*id-i],p[2*id-i]是j的回文半径,如果这个半径小于mx-i,又因为j和i对称,p[i]=p[j];
2.如果mx-i小于p[2*d-i],发现mx管不住所有原来关于j对称的,所以只能取到mx-i,后面的怎么办呢 ?
我们加个循环判断一下即可!
while (t[i - p[i]] == t[i + p[i]])++p[i];
从这个半径开始向两边延伸,如果是回文,半径加一。
然后就是要维护这个mx使之成为能延伸到最右端的位置。
if (mx < i + p[i])
{
mx = i + p[i];
id = i;
}
完整代码如下:
//manacher 求最长回文子串长度
#include<iostream>
#include<vector>
#include<string>
#include<algorithm>
#include<cstdio>
using namespace std;
char a[110010];
int manacher(string s)
{
int lens = s.size();
string t = "$#";
for (int i = 0;i < lens;i++)
{
t += s[i];
t += "#";
}
int lent = t.size();
vector<int>p(lent, 0);
int mx = 0, len = 0, id = 0, center = 0;
for (int i = 0;i < lent;i++)
{
p[i] = mx - i ? min(p[2 * id - i], mx - i) : 1;
while (t[i - p[i]] == t[i + p[i]])++p[i];
if (mx < i + p[i])
{
mx = i + p[i];
id = i;
}
if (len < p[i])
{
len = p[i];
center = i;
}
}
return len - 1;
}
int main()
{
while (scanf("%s",a)!=EOF)
{
printf("%d\n", manacher(a));
}
return 0;
}