最长回文子串-Manacer算法
2016-04-26 23:38
691 查看
一个字符串中连续的一段就是这个字符串的子串,而回文串指的是12421这种从前往后读和从后往前读一模一样的字符串,所以最长回文子串的意思就是这个字符串中最长的为回文串的子串。很容易想到暴力的解法,一个一个枚举回文串的起始位置。
举个例子:
原来的串: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<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)); } }
相关文章推荐
- JAVA每日总结
- JVM 类型的生命周期学习
- Redis的AOF持久化-Redis学习笔记四
- sass、less和stylus的安装使用和入门实践
- c++上机报告4
- Java 接口和抽象类区别
- linux修改用户目录文件名
- Mysql5.6配置文件详解
- 贪心算法经典例子
- Atitit.增强系统稳定性----虚拟内存的设置
- 工具函数(二)
- Atitit.增强系统稳定性----虚拟内存的设置
- c++ 11(四)
- 为什么跑步比赛是逆时针跑?
- C++实验4-穷举法解决组合问题
- c++课后作业4
- 第 10 章 表单元素[下]
- Atitit.md5 实现原理
- Atitit.增强系统稳定性----虚拟内存的设置
- Atitit.md5 实现原理