您的位置:首页 > 其它

最长回文子串-Manacer算法

2016-04-26 23:38 691 查看
一个字符串中连续的一段就是这个字符串的子串,而回文串指的是12421这种从前往后读和从后往前读一模一样的字符串,所以最长回文子串的意思就是这个字符串中最长的为回文串的子串。很容易想到暴力的解法,一个一个枚举回文串的起始位置。

#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
char str[1000010];

int main()
{
int n;
cin>>n;
while(n--)
{
memset(str,0,sizeof(str));
cin>>str;
int ans;
int i,j,k;
bool flag;
int len=strlen(str)-1;
for(i=len;i>=0;i--)
{
for(j=0;j<=len;j++)
{
k=j+i;
if(k<=len)
{
flag=1;
int k1=k;
int j1=j;
while(k1>j1)
{
if(str[k1]!=str[j1]) flag=0;
k1--;
j1++;
}
if(flag)
{
ans=i+1;
goto end;
}
}
}
}
end:cout<<ans<<endl;
}
}
在这个算法中存在大量重复的计算。如果一个字符串的[3, 7]这一段已经不是回文子串了,[2, 8]这一段就不可能是回文子串了。我们可以对这个算法进行改进,枚举回文串的中点,从中点向两边扩展。值得注意的是回文串有奇数长和偶数长两种情况,对于这两种情况我们要分别进行讨论。
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
char str[1000010];

int main()
{
int n;
cin>>n;
while(n--)
{
memset(str,0,sizeof(str));
cin>>str;
int ans;
int i,j,k,maxans=1;
bool flag;
int len=strlen(str)-1;
//回文串的长度是奇数
for(i=0;i<=len;i++)
{
ans=1;
j=i+1;
k=i-1;
while(j<=len&&k>=0)
{
if(str[k]==str[j])
{
k--;
j++;
ans+=2;
}
else break;
}
if(ans>maxans) maxans=ans;
}
//回文串的长度是奇数
for(i=0;i<=len;i++)
{
j=i+1;
if(str[i]==str[j])
{
ans=2;
j=j+1;
k=i-1;
while(j<=len&&k>=0)
{
if(str[k]==str[j])
{
k--;
j++;
ans+=2;
}
else break;
}
if(ans>maxans) maxans=ans;
}
}
cout<<maxans<<endl;
}
}
下面该轮到我们要隆重介绍的Manacer算法登场了。前面我们说过枚举回文串的中点需要分情况讨论,如果在原来的字符串的前后以及每两个字符之间添加'#'字符(前提是这个'#'不在原串中出现过),显然现在所有的回文串的长度都是奇数的,这样就不用分情况讨论了,这是这个算法非常精妙的地方。我们还需要一个辅助数组p记录以每个字符为核心的最长回文字符串半径。p[i]最小为1,此时回文字符串就是字符串本身。
举个例子:

原来的串:waabwswfd

现在的串:#w#a#a#b#w#s#w#f#d#

辅助数组:1212321212141212121

注意到,P[i]-1就是该回文子串在原串中的长度。接下来就是最关键的部分了。设id的回文串的半径是r[id],那么此时id的回文串右边要延展到right=id+r[id]-1这个字符。在计算(id, right]中的某个点x的时候,可以发现x的关于id的对称点x'=2*id-x的回文串是已经计算过的,利用x'的回文的性质,我们计算x的半径的时候,就不用从1开始枚举,而是从min(r[x'], right-x+1)开始枚举。整个过程中采用贪心法,也就是说选取right尽可能大的id使得后面的中心尽量少的扩展。自己动手画个图就很好理解了。

POJ3974就是这样的一道模板题,贴上AC代码:
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
using namespace std;
const int MAX=1000010;
char s[MAX];
char ss[MAX<<1];
int p[MAX<<1];

int solve(int len)
{
int ans=0;
int right=-1;
int id=-1;
for(int i=0;i<len;i++)
{
int r=1;
if(right>=i) r=max(r,min(right-i+1,p[2*id-i]));
while((i-r+1>=0&&i+r-1<len)&&(ss[i-r+1]==ss[i+r-1])) r++;
r--;
if(i+r-1>right)
{
right=i+r-1;
id=i;
}
p[i]=r;
if(ans<r) ans=r;
}
return ans-1;
}

int main()
{
int Case=1;
while(scanf("%s",s)!=EOF)
{
if(strcmp(s,"END")==0)
break;
int len=strlen(s);
int cnt=0;
for(int i=0;i<len;i++)
{
ss[cnt++]='#';
ss[cnt++]=s[i];
}
ss[cnt++]='#';
printf("Case %d: %d\n",Case++,solve(cnt));
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: