原创作品,出自 “晓风残月xj” 博客,欢迎转载,转载时请务必注明出处(http://blog.csdn.net/xiaofengcanyuexj)。
由于各种原因,可能存在诸多不足,欢迎斧正!
何为回文串?
众所周知,回文串就是左右对称的字符串。
给定一个字符串,要求其中的最长回文子串。这是一个比较简单基本的问题。
1.暴力枚举。
枚举以每个字符为中心,看左右延伸的最长回文串,这里注意分奇偶,总的时间复杂度为O(N^N) .这是最简单最基本的算法,通常是不可取的。
#include"iostream"
#include"cstdio"
#include"cstring"
using namespace std;
const int MAXN=1000000+100;
char str[MAXN*2];//待处理字符串
int Solve()
{
int i,l,tmp,ans=-1,len=strlen(str);
for(i=0;str[i];i++)
{
l=0;tmp=1;
while(i-l-1>=0&&i+l+1<len)//奇数时
{
if(str[i-l-1]==str[i+l+1])
l++,tmp+=2;
else break;
}
if(ans<tmp)
ans=tmp;
l=0;tmp=0;
while(i-l>=0&&i+l+1<len)//偶数时,只算左边;右边的可以是下一个的左边
{
if(str[i-l]==str[i+l+1])
l++,tmp+=2;
else break;
}
if(ans<tmp)
ans=tmp;
}
return ans;
}
int main()
{
int tag=1;
while(~scanf("%s",str))
{
if(strcmp(str,"END")==0)break;
printf("Case %d: %d\n",tag++,Solve());
}
return 0;
}
2.后缀数组。
将原字符串翻过来接在新字符串的后面,中间用某一个特殊的字符串隔开,这样问题就变成了求新字符串分属两部分的后缀的最长前缀。时间复杂度为O(N*log(N)),个人觉得一般问题是可以解决的。
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
//*****************************************************************
const int MAXN=(1000000+100)*2;
char str[MAXN];//待处理字符串
int sa[MAXN];//求得的后缀数组
int wa[MAXN],wb[MAXN],wv[MAXN],wh[MAXN];
int cmp(int *r,int a,int b,int l)
{
return r[a]==r[b]&&r[a+l]==r[b+l];
}
//求后缀数组sa[],下标1到n-1(此处n=strlen(str)+1)有效后缀
//将str的n个后缀从小到大进行排序之后把排好序的后缀的开头位置顺次放入sa中。
//保证Suffix(sa[i])<Suffix(sa[i+1])
//1<=i<n,sa[0]存放人为添加在末尾的那个最小的后缀
//倍增算法的时间复杂度为O(nlogn)
//倍增算法的空间复杂度都是O(n)
void da(char *r,int *sa,int n,int m)
{
int i,j,p,*x=wa,*y=wb,*t;
for(i=0;i<m;i++) wh[i]=0;
for(i=0;i<n;i++) wh[x[i]=r[i]]++;
for(i=1;i<m;i++) wh[i]+=wh[i-1];
for(i=n-1;i>=0;i--) sa[--wh[x[i]]]=i;
for(j=1,p=1;p<n;j*=2,m=p)
{
for(p=0,i=n-j;i<n;i++) y[p++]=i;
for(i=0;i<n;i++) if(sa[i]>=j) y[p++]=sa[i]-j;
for(i=0;i<n;i++) wv[i]=x[y[i]];
for(i=0;i<m;i++) wh[i]=0;
for(i=0;i<n;i++) wh[wv[i]]++;
for(i=1;i<m;i++) wh[i]+=wh[i-1];
for(i=n-1;i>=0;i--) sa[--wh[wv[i]]]=y[i];
for(t=x,x=y,y=t,p=1,x[sa[0]]=0,i=1;i<n;i++)
x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
}
return;
}
int rank[MAXN],height[MAXN];
//定义height[i]=suffix(sa[i-1])和suffix(sa[i])的最长公
//共前缀,也就是排名相邻的两个后缀的最长公共前缀
//任意两个起始位置为i,j(假设rank[i]<rank[j])的后缀的最长公共前缀
//为height[rank[i]+1]、height[rank[i]+2]…height[rank[j]]的最小值
void calheight(char *r,int *sa,int n)
{
int i,j,k=0;
for(i=1;i<=n;i++) rank[sa[i]]=i;
for(i=0;i<n;height[rank[i++]]=k)
for(k?k--:0,j=sa[rank[i]-1];r[i+k]==r[j+k];k++);
return;
}
int main()
{
int len,i,ans,tag=1;
while(~scanf("%s",str))
{
if(strcmp(str,"END")==0)break;
len=strlen(str);
str[len]='#';
for(i=0;i<len;i++)
str[len+1+i]=str[len-i-1];
str[len+len+1]=0;
da(str,sa,len*2+2,250);
calheight(str,sa,len*2+1);
ans=1;
for(i=1;i<=len*2+1;i++)
{
if((sa[i]-len)*(sa[i-1]-len)<0&&height[i]>ans&&sa[i]+sa[i-1]+height[i]==len*2+1)
ans=height[i];
}
//printf("str=%s\n",str);
printf("Case %d: %d\n",tag++,ans);
}
return 0;
}
3.Manacher算法。
这是我这篇文章要注重介绍的算法,这是个求回文串的高效的算法,时间复杂度有效的降到O(N),这是普遍可以接受的。
Manacher算法主要思想:
1.在原字符的间隙间插入一个未曾出现的字符,从而将字符串的长度变为奇数。如abab变化之后为#a#b#a#b#。
2.开辟一个数组num[i],记录以该位置中心的回文串向右延伸的长度(包括本身),则以该字符为中心的回文串的长度为num[i]-1.如#a#b#a#b#,对应的num为1,2,1,4,1,4,1,2,1。
3.转移规则,也就是该算法的核心。若当前要求num[i],假设之前已经求出的延伸至最右端的位置为MxR,则对应的回文串的中心位置为pos;设j为i关于pos的对称位置,则有若MxR>i,num[i]>=Min(num[j],MxR-i),否则num[i]=1(其中j=2*pos-i);之后再循环比较str[i-num[i]]与str[i+num[i]],若相等,num[i++](说明回文串可以向左右两边延伸);否则跳出。为什么会这样呢?若MxR>i,则说明i是以pos为中心位置的回文串的一部分;又j、i关于pos对称,由回文串的左右对称的属性,num[i]>=Min(num[j],MxR-i);之后就是延伸,找出num[i]了。从而保证在线性时间复杂度O(N)内求出以每个为中心的最长回文串了。具体的参考一下转载大神的图:(此图中mx=MxR,id=pos)
#include"iostream"
#include"cstdio"
#include"cstring"
using namespace std;
const int MAXN=1000000+100;
char str1[MAXN*2],str2[MAXN*2];//待处理字符串
int num[MAXN*2];
//将str1变成str2,如abab变成$#a#b#a#b#
void init()
{
int i,id;
str2[0]='$';
str2[1]='#';
for(i=0,id=2;str1[i];i++,id+=2)
{
str2[id]=str1[i];
str2[id+1]='#';
}
str2[id]=0;
}
//Manacher算法求最长回文子串,时间复杂度为O(N)
int Manacher()
{
int i,ans=0,MxR=0,pos;
for(i=1;str2[i];i++)
{
if(MxR>i)num[i]=num[pos*2-i]<(MxR-i)?num[pos*2-i]:(MxR-i);
else num[i]=1;
while(str2[i+num[i]]==str2[i-num[i]])
num[i]++;
if(num[i]+i>MxR)
{
MxR=num[i]+i;
pos=i;
}
if(ans<num[i])
ans=num[i];
}
return ans-1;
}
int main()
{
int ans,tag=1;
while(~scanf("%s",str1))
{
if(strcmp(str1,"END")==0)break;
init();
ans=Manacher();
printf("Case %d: %d\n",tag++,ans);
}
return 0;
}
具体应用见http://blog.csdn.net/xj2419174554/article/details/10295755
http://blog.csdn.net/xj2419174554/article/details/10295917
从上所述,求最长回文子串大概有这三种方法,可能在暴力求的过程中能用二分法加速。
以上如有错误的地方,请斧正。此文参考http://www.cnblogs.com/wuyiqi/archive/2012/06/25/2561063.html在此表示感谢!