引子:给定一个字符串s,让你求出最长的回文子串的长度。
算法大致实现过程:
一:为了消除回文字符串长度奇或偶的影响。先在每两个字符之间插入一个原字符串没有出现过的字符(这里就用#)构成新串str。设p[i] 为以str[i]字符为中心的回文字符串的最大半径。
二:从前到后递推p数组。
三:枚举所有p[i]值,更新最大值。
Manacher精华——求p[]数组。首先我们在求p[i]时,已经求出前面的p[j]值(0<=j<=i-1)
求p[i]的准备:
用mx记录 max{ k+p[ k ] } (0<=k<=i-1) ——前面所有回文字符串能覆盖到的最右边的位置。
用id记录mx取最大值时的k ——前面所有回文字符串中能覆盖到最右边位置 的那个以字符str[id]为中心的回文串。
根据前面的p[]求p[i]
1. mx > i ——以str[id]为中心的回文字符串把字符str[i]覆盖到了
这个情况下我们可以得到 p[i]的值一定不小于p[j]和mx-i的最小值,其中j与i关于id对称即j=2*id-i
2. mx <= i ——以str[id]为中心的回文字符串以字符str[i]结尾或者没有覆盖到字符str[i]
这种情况下p[i]的值最小为1
3. 根据p[i]向左右进行拓展,直到不能延伸为止
while(str[ i + p[i] ] == str[ i - p[i] ]) p[i]++;
4. 每次求出p[i]后,可以更新维护id的值
代码实现:
#include <cstdio>
#include <cstring>
#include <algorithm>
#define MAXN 110100
using namespace std;
char s[MAXN];//原串
int str[MAXN*2];//新串 注意数组大小
int p[MAXN*2];
void Manacher(char *T)
{
int len = strlen(T);
int l = 0;
str[l++] = '@';//防止越界
str[l++] = '#';
for(int i = 0; i < len; i++)
{
str[l++] = T[i];
str[l++] = '#';
}
str[l] = 0;
int mx = 0, id = 0;
int ans = 0;
for(int i = 0; i < l; i++)
{
if(mx > i)//2*id-i 为 i关于id的对称点
p[i] = min(p[2*id - i], mx-i);
else
p[i] = 1;
//左右延伸
while(str[i+p[i]] == str[i-p[i]]) p[i]++;
if(i + p[i] > mx)//找计算p[i+1]用到的id
{
mx = i + p[i];
id = i;
}
ans = max(p[i]-1, ans);
}
printf("%d\n", ans);
}
int main()
{
while(scanf("%s", s) != EOF)
{
Manacher(s);//求字符串s的 最长回文子串长度
}
return 0;
}
本人数据结构很渣,有错误的地方欢迎指正。 (⊙o⊙)