算法相关
目的:求最长回文子串的长度。
回文串:“回文串”是一个正读和反读都一样的字符串,比如“level”或者“noon”等等就是回文串。
暴力算法:枚举子串,检查是否回文 O(n^3)
暴力算法+:枚举每一个字符及空隙作为对称轴,暴力求回文子串长度 O(n^2)
Manacher:Manachar算法主要是处理字符串中关于回文串的问题的,它可以在 O(n) 的时间处理出以字符串中每一个字符为中心的回文串半径,由于将原字符串处理成两倍长度的新串,在每两个字符之间加入一个特定的特殊字符,因此原本长度为偶数的回文串就成了以中间特殊字符为中心的奇数长度的回文串了。
算法流程
定义
t[]:原字符串
s[]:预处理后的字符串
r[i]:在s中,以i为对称轴,能够扩展的最长的对称半径(不包括i字符本身)
预处理
为了避免偶数长度的回文串对称轴是字符间隙的情况,我们在字符之间插入与题目无关的字符(如‘#’)
eg1:
a b a
# a # b # a #
r 0 1 0 3 0 1 0
eg2:
a b b a
# a # b # b # a #
r 0 1 0 1 4 1 0 1 0
由示例易知,在s串中的r数组,就是在t串中的以i为对称轴的回文子串的长度;
求最长回文子串,只需求得最大的r即可
算法流程
定义变量pos为在1~i中的一个对称轴,满足pos+r[pos]最大,并将pos+r[pos]记做max_r
显然pos < i,处理到i字符
考虑i与max_r的关系
当i < max_r 时,记i关于pos的对称点为j,
(j+i)/2=pos -> j=pos*2-i;
1.i < max_r &&r[j]+i>max_r
由pos和max_r的对称定义可知,r[i]至少为i~max_r,暴力匹配 max_r以后的字符
更新 pos+max_r
2. i < max_r&&r[j]+i<=max_r
由对称可知 , r[i]=r[j];
3. i >= max_r
暴力按位匹配,求得r[i],并 更新 pos+max_r
在代码中,三种情况并没有详细划分
模板
void Manacher(){
for(int i=0;i<len_t;i++){
s[i<<1]='#';
s[i<<1|1]=t[i];
}
len=len_t*2+1;
s[len-1]='#';
//预处理结束
int pos=0,max_r=0;
for(int i=0;i<len;i++){
if(i<max_r) r[i]=min(max_r-i,r[pos*2-i]);
//第1,2种情况,取min初始化r 对称点j
else r[i]=0;
//第3种情况
while(i+r[i]+1<len&&i-r[i]-1>=0&&s[i+r[i]+1]==s[i-r[i]-1]) r[i]++;
// 左右不越界 拓展的左右相等 更新r
if(r[i]+i>max_r){
//更新pos+max_r
max_r=r[i]+i;
pos=i;
}
}
return ;
}
例题
https://vjudge.net/problem/POJ-3974
代码
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=1000000+500;
int len_t,len,ans,cnt,r[N<<1];
string t;
char s[N<<1];
void manacher(){
for(int i=0;i<len_t;i++) {
s[i<<1]='#';
s[i<<1|1]=t[i];
}
len=len_t*2+1;
s[len-1]='#';
int max_r=0,pos=0;
for(int i=0;i<len;i++){
if(i<max_r) r[i]=min(max_r-i,r[pos*2-i]);
else r[i]=0;
while(i+r[i]+1<len&&i-r[i]-1>=0&&s[i+r[i]+1]==s[i-r[i]-1]) r[i]++;
if(i+r[i]>max_r){
max_r=i+r[i];
pos=i;
}
ans=max(ans,r[i]);
}
return ;
}
void init(){
len_t=t.size();
ans=-1;
memset(r,0,sizeof(r));
}
int main(){
while(cin>>t){
if(t=="END") return 0;
cnt++;
printf("Case %d: ",cnt);
init();
manacher();
printf("%d\n",ans);
}
return 0;
}