算法详解
Manacher算法能够在线性的时间内求解最长回文子串,可以说实现的非常巧妙,他的基本思想是回文子串从中间向两边扩展一定是相同的,那么首先我们要对字符串进行一定的预处理:
比如:字符串:aaaa aba
那么他们长度分别为偶数和奇数,因为偶数的话,我们找不到一个中点使得两边相同,那么我们在每两个字符之间添加一个不会出现的字符,使得他们全部变成奇数个:
#a#a#a#a# #a#b#a# 这样就一定可以找到一个最长回文串的中点了
我们令p[i],表示以i点为中心,可以拓展到最右边的长度,因为我们添加了特殊符号#,所以以i点为中心的最长回文串长度就应该为p[i]-1,maxlen表示:可以拓展的最远距离(遍历过所有点中,可以拓展的最长距离,数组p中最大的数值)
现在我们要从头开始遍历字符串,来计算p[i],用p[i]来不断更新maxlen,那么因为我们添加了'#'的原因,我们最终的答案就是maxlen-1
假设我们当前遍历的位置为k+i,并且k前面所有点中i为拓展到的最远距离,即maxlen = p[i]
后面的情况分为两种:
1) 当k+i > maxlen + p[i] ,当前节点不被前面检测到的的回文串所包含,那么就初始化p[k+i] = 1,然后从这个点向两边遍历求解最长回文字串:
while(s[k + p[k+i]] == s[k - s[k+i]) p[k+i]++;
2) 当 k+i < maxlen + p[i] 时 当前节点被前面的回文串所包含
①当和与之对应的点i-k回文串左端大于i的左端点时,以i-k为中心的回文串一部分在以i为中心的回文串的左边
这样的话,我们的p[i+k] 的长度一定为i+k到i右端点的长度,即:p[i+k] = p[i] - k(i-k到最左端的长度),因为如果更长的话,那么i的长度一定不止现在的长度,还是可以向右延伸的
②当和与之对应的点i-k回文串左端大于i的左端点时,以i-k为中心的回文串全部在以i为中心的回文串的右边
这时p[i+k] = p[i-k]
③当和与之对应的点i-k回文串左端等于i的左端点时
这时p[i+k]的要大于等于p[i-k],然后我们需要继续判断while(s[i+k+p[i+k]] == s[i+k - p[i-k]] ) p[i+k] ++;
我们总结可以发现:
p[i+k] = min(p[i] - k , p[i - k]);
while(s[i+k+p[i+k]] == s[i+k - p[i-k]] ) p[i+k] ++;
那么这样遍历到字符串结尾就可以求解出最终的答案
栗子及模板
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdio>
using namespace std;
const int N = 110000 + 10;
int main()
{
char s[N*2];
int p[N*2];
while(~scanf("%s",s))
{
int len = strlen(s);
for(int i = len;i >= 0;i --)
{
s[i+i+2] = s[i];
s[i+i+1] = '#';
}
s[0] = '*';
int maxlen = 0,id = 0;
for(int i = 2;i < len*2+1 ;i ++)
{
if(p[id]+id > i) p[i] = min(p[id*2-i] , p[id] + id - i);
else p[i] = 1;
while(s[i + p[i]] == s[i - p[i]]) p[i] ++;
if(p[id]+id < p[i]+i) id = i;
if(maxlen < p[i]) maxlen = p[i];
}
printf("%d\n",maxlen-1);
}
return 0;
}
参考博客
https://blog.csdn.net/xingyeyongheng/article/details/9310555