题目地址:
https://www.acwing.com/problem/content/description/3190/
给定一个长度为 n n n的由小写字母构成的字符串,求它的最长回文子串的长度是多少。
输入格式:
一个由小写字母构成的字符串。
输出格式:
输出一个整数,表示最长回文子串的长度。
数据范围:
1
≤
n
≤
1
0
7
1≤n≤10^7
1≤n≤107
可以用Manachar算法。先将整个字符串做如下处理,将这个字符串每两个字符之间插入一个不在字母表里的字符,比如#
,然后在首尾也添加#
,再在首尾添加两个不同字符,不能是字母表或者是#
,比如可以在开头添加$
结尾添加^
。例如,对于字符串aab
,处理完之后字符串就变为了$#a#a#b#^
。令处理完的字符串为
t
t
t。那么,每个
s
s
s里的回文子串都能对应于
t
t
t的某个长度为奇数的回文子串(我们要求在
t
t
t里取的子串以#
开头结尾),接下来考虑对
t
t
t求每个位置为中心的情况下最大的回文子串半径,设
p
[
i
]
p[i]
p[i]为以
t
[
i
]
t[i]
t[i]为中心的最长回文子串的半径,
p
[
0
]
=
0
p[0]=0
p[0]=0,为无效值。同时维护两个变量
m
r
mr
mr和
m
i
mi
mi,分别是右端点最靠右的最长回文子串的中点位置和右端点位置 + 1,接下来递推。从左向右遍历到
t
[
i
]
t[i]
t[i],如果
i
<
m
r
i<mr
i<mr,那么找到
j
=
2
m
i
−
i
j=2mi-i
j=2mi−i,我们证明
p
[
i
]
≥
min
{
p
[
j
]
,
m
r
−
i
}
p[i]\ge \min\{p[j], mr-i\}
p[i]≥min{p[j],mr−i}。如果
p
[
j
]
≤
m
r
−
i
p[j]\le mr-i
p[j]≤mr−i,那么以
i
i
i为中点的最长回文子串半径
p
[
i
]
p[i]
p[i]显然可以取到
p
[
j
]
p[j]
p[j],并且在以
j
j
j为中点的最长回文子串到达边界的时候,
p
[
i
]
p[i]
p[i]有可能更大;如果
p
[
j
]
>
m
r
−
i
p[j]>mr-i
p[j]>mr−i,那么以
i
i
i为中点的最长回文子串右边界可以取并且最多取到
m
r
mr
mr,否则就会得出
m
r
mr
mr可以继续拓展的结论,矛盾。无论怎样,
p
[
i
]
p[i]
p[i]都可以从
min
{
p
[
j
]
,
m
r
−
i
}
\min\{p[j], mr-i\}
min{p[j],mr−i}开始递增枚举。如果
i
≥
m
r
i\ge mr
i≥mr,那么
p
[
i
]
p[i]
p[i]可以从
1
1
1开始枚举。当枚举停下来的时候(即再扩大就不回文的时候),更新
m
r
mr
mr和
m
i
d
mid
mid。求得的所有
p
[
i
]
p[i]
p[i]的最大值如果是
m
p
mp
mp的话,容易验证,
m
p
−
1
mp-1
mp−1就是
s
s
s的最长回文子串的长度。代码如下:
#include <iostream>
#include <cstring>
using namespace std;
const int N = 2e7 + 10;
int n;
char a[N], b[N];
int p[N];
int res;
// 构造新串
void init() {
int k = 0;
b[k++] = '$', b[k++] = '#';
for (int i = 0; i < n; i++) b[k++] = a[i], b[k++] = '#';
b[k++] = '^';
n = k;
}
void manacher() {
int mr = 0, mid;
// 最后的'^'就不用枚举了
for (int i = 1; i < n - 1; i++) {
// 初始化p[i]
if (i < mr) p[i] = min(p[mid * 2 - i], mr - i);
else p[i] = 1;
// 向外扩展
while (b[i - p[i]] == b[i + p[i]]) p[i]++;
if (i + p[i] > mr) {
mr = i + p[i];
mid = i;
}
res = max(res, p[i] - 1);
}
}
int main() {
scanf("%s", a);
n = strlen(a);
init();
manacher();
printf("%d\n", res);
return 0;
}
时空复杂度 O ( n ) O(n) O(n)。