引入
给定一个字符串,求其最大回文子串长度。
对于
30
30
30%的数据,
1
<
=
1<=
1<=字符串长度
<
=
1000
<=1000
<=1000
对于
100
100
100%的数据,
1
<
=
1<=
1<=字符串长度
<
=
1
0
7
<=10^7
<=107
对于30%的数据
枚举每一个字符,然后同时向两边延伸,最后不一样时就停止,更新答案。
int ans=0;
for(int i=0;i<strlen(s);i++){
int x=1; //指针
while(i+x<=n&&i-x>=0&&s[i+x]==s[i-x]) x++; //向两边延伸
ans=max(ans,2*x-1); //更新答案
}
对于100%的数据
上面的方法最坏复杂度为 O ( n 2 ) O(n^2) O(n2),并不适用于 1 0 7 10^7 107的数据,这时,我们就要新方法优化它。
Manacher算法
基本原理
现在有一个回文串:
A
A
B
B
C
C
D
D
C
C
B
B
A
A
AABBCCDDCCBBAA
AABBCCDDCCBBAA
将其分隔开来,变为
∣
A
∣
A
∣
B
∣
B
∣
C
∣
C
∣
D
∣
D
∣
C
∣
C
∣
B
∣
B
∣
A
∣
A
∣
|A|A|B|B|C|C|D|D|C|C|B|B|A|A|
∣A∣A∣B∣B∣C∣C∣D∣D∣C∣C∣B∣B∣A∣A∣
其回文中心为中间
D
∣
D
D|D
D∣D间的分隔符。
不难发现,在回文中心两侧并且离回文中心等距离的一段区间中一段区间是另一段区间的倒置
For example:
A
∣
B
∣
B
∣
C
与
C
∣
B
∣
B
∣
A
A|B|B|C与C|B|B|A
A∣B∣B∣C与C∣B∣B∣A
所以,如果一个大回文串中包含了一个小回文串,那么在对称中心的另一侧必将出现另一个回文串。
例如
A
B
B
A
C
D
C
A
B
B
A
ABBACDCABBA
ABBACDCABBA
我们找到了左边的串
A
B
B
A
ABBA
ABBA,然后由对称中心
D
D
D对称过去,就会发现一个相同的回文串
那如果我们找到了右边的回文串,左边的也同理可以找到。
这就是
M
a
n
a
c
h
e
r
Manacher
Manacher算法的基本原理。
实现
由于回文中心可能在两个字母中间,如
A
B
B
A
ABBA
ABBA。
所以开始先将字符串用一些字符分割开。
然后以每一个点作为回文中心开始扩展。
m
i
d
mid
mid与
r
r
r是指当前最右边的回文串的中心与右边界。
如果当前点
i
i
i在
m
i
d
mid
mid与
r
r
r之间
那么就会出现上面描述的情况。
用一个
f
f
f数组记录以每个点为中心的回文串最长长度。
如果出现这种情况,直接可以使用这个数组,再判断是否超过
r
r
r,如果超过
r
r
r,则右边界只能到
r
r
r。
然后在向两边扩展,记录
f
[
i
]
f[i]
f[i],如果右边界超过
r
r
r,就更新
m
i
d
mid
mid与
r
r
r。
如此循环。
最后统计答案,由于扩展的最后一定是一个分隔符,所以
a
n
s
=
m
a
x
(
a
n
s
,
f
[
i
]
−
1
)
ans=max(ans,f[i]-1)
ans=max(ans,f[i]−1)
代码
#include<bits/stdc++.h>
using namespace std;
char s1[11000005],s2[22000005];
int f[22000005],len;
void pre(){
int Len=strlen(s1);
s2[++len]='#';
for(int i=0;i<Len;i++){
s2[++len]=s1[i];
s2[++len]='#';
}
s2[len+1]='\0';
}
void manacher(){
int mid=0,r=0;
for(int i=1;i<=len;i++){
int x;
if(r<=i) x=1;
else x=min(f[mid*2-i],r-i+1);
while(s2[i-x]==s2[i+x]) x++;
if(i+x-1>r){
r=i+x-1,mid=i;
}
f[i]=x;
}
}
signed main(){
scanf("%s",s1);
pre();
manacher();
int ans=0;
for(int i=1;i<=len;i++){
ans=max(ans,f[i]-1);
}
cout<<ans<<endl;
}
复杂度分析
由于 r r r之内的部分不会被再次匹配,而 r r r是一直向右增长,最多增长 n n n次,所以复杂度为 O ( n ) O(n) O(n)。