算法详解
Manacher算法能够在线性的时间内求解最长回文子串,可以说实现的非常巧妙,他的基本思想是回文子串从中间向两边扩展一定是相同的,那么首先我们要对字符串进行一定的预处理:
比如:字符串:aaaa aba
那么他们长度分别为偶数和奇数,因为偶数的话,我们找不到一个中点使得两边相同,那么我们在每两个字符之间添加一个不会出现的字符,使得他们全部变成奇数个:
#a#a#a#a# #a#b#a# 这样就一定可以找到一个最长回文串的中点了
我们令p[i],表示以i点为中心,可以拓展到最右边的长度,maxlen表示:可以拓展的最远距离(遍历过所有点中,可以拓展的最长距离)
现在我们要从头开始遍历字符串,来计算p[i],用p[i]来不断更新maxlen,那么因为我们添加了'#'的原因,我们最终的答案就是maxlen-1
假设我们当前遍历的位置为k+i,并且k前面所有点中i为拓展到的最远距离,即maxlen = p[i]
后面的情况分为两种:
1) 当k+i > maxlen ,我们找不到与这个点相对应的点,那么就初始化p[k+i] = 1,然后从这个点向两边遍历求解最长回文字串:
while(s[k + p[k+i]] == s[k - s[k+i]) p[k+i]++;
2) 当 k+i < maxlen 时
①当和与之对应的点i-k回文串左端大于i的左端点时
这样的话,我们的p[i+k] 的长度一定为i+k到i右端点的长度,即:p[i+k] = p[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; }