Manacher算法 - 最长回文子串
2016-03-13 17:43
351 查看
如题,求字符串S的最长回文子串,暴力就不说了。
Manacher复杂度:O(n)。
首先,求回文的话奇数长度字符串和偶数长度字符串处理方式不同,为解决该问题,我们在字符串S每个字符后加上分隔符'#',同时为了减少边界判断,在字符串的最前面加个'$#',当然字符串本身不能出现过'$'和'#'。
mx:以id为中心的最长回文能到达的最远距离
p[i]:以s[i]为中心的最长回文往一侧能延伸的距离(包含s[i]自身)
以上回文的判断均含 分割符
如上,p[4]为2是"b#",然后有结论原串最长回文长度 = max{ p[i] } - 1,证明略。那么问题即为如何求p[i]。
假设现id为已知最长回文的中心点,i是当前下标,j为i以id为中心的对称点,j 的回文情况已知(根据对称得下标为2 * id - i )。
分两种大情况。
情况一:mx > i
子情况一:mx - i >= p[ j ],即 i到mx距离 >= Lj到Cj距离
![](http://img.blog.csdn.net/20160313165039267)
图中紫色部分为i ,j各自的回文范围,则因为[ Lj...Cj ]和[ Cj...Rj ]回文,[ j...id ] 和[ id...i ] 回文,所以 [ Lj...Cj ]与[ Li...Ri ]回文,即p[ i ] >= p[ j ],代码先直接赋值为p[ j ],具体的值之后在暴力找就行了。
子情况二:mx - i < p[ j ],即 i到mx距离 < Lj到Cj距离
![](http://img.blog.csdn.net/20160313172926630)
此时Lj在mx'的左边,而mx'左边和mx右边已经没有回文特征了,无法得出p[ i ] >= p[ j ]的结论,但p[ i ] 肯定超出mx,因为mx范围内还是有以id为中心的回文特征的,所以p[ i ] >= mx - i。
情况二:mx < i,此时没有任何已知信息可以用,只能p[ i ] = 1开始。
更新id的条件是找到更远的mx,因为mx更远可以获取更多信息,当然设置为p[i]更大为条件答案也对,不过会慢很多。
以上内容表示为代码:
也即
至此就可以写出完整代码了。
无注释版
练习地址:http://acm.hdu.edu.cn/showproblem.php?pid=3068
英文教程链接(里面图不错,虽然内容好像有点错的):http://articles.leetcode.com/longest-palindromic-substring-part-ii
Manacher复杂度:O(n)。
首先,求回文的话奇数长度字符串和偶数长度字符串处理方式不同,为解决该问题,我们在字符串S每个字符后加上分隔符'#',同时为了减少边界判断,在字符串的最前面加个'$#',当然字符串本身不能出现过'$'和'#'。
abcbaaaabbc $#a#b#c#b#a#a#a#a#b#b#c#
变量说明:
id: 目前已知的最长回文的中心点mx:以id为中心的最长回文能到达的最远距离
p[i]:以s[i]为中心的最长回文往一侧能延伸的距离(包含s[i]自身)
以上回文的判断均含 分割符
下标 0 1 2 3 4 5 6 7 8 9... 串 $ # a # b # c # b # a # a # a # a # b # b # c # p[i] 0 1 2 1 2 1 6 1 2 1 2 3 4 7 4 3 2 1 2 3 2 1 2 1
如上,p[4]为2是"b#",然后有结论原串最长回文长度 = max{ p[i] } - 1,证明略。那么问题即为如何求p[i]。
算法思想:
根据已知信息确定p[i]的最小值,即p[i] >= ?,就不用从1开始再算了,从而省时间。假设现id为已知最长回文的中心点,i是当前下标,j为i以id为中心的对称点,j 的回文情况已知(根据对称得下标为2 * id - i )。
分两种大情况。
情况一:mx > i
子情况一:mx - i >= p[ j ],即 i到mx距离 >= Lj到Cj距离
图中紫色部分为i ,j各自的回文范围,则因为[ Lj...Cj ]和[ Cj...Rj ]回文,[ j...id ] 和[ id...i ] 回文,所以 [ Lj...Cj ]与[ Li...Ri ]回文,即p[ i ] >= p[ j ],代码先直接赋值为p[ j ],具体的值之后在暴力找就行了。
子情况二:mx - i < p[ j ],即 i到mx距离 < Lj到Cj距离
此时Lj在mx'的左边,而mx'左边和mx右边已经没有回文特征了,无法得出p[ i ] >= p[ j ]的结论,但p[ i ] 肯定超出mx,因为mx范围内还是有以id为中心的回文特征的,所以p[ i ] >= mx - i。
情况二:mx < i,此时没有任何已知信息可以用,只能p[ i ] = 1开始。
更新id的条件是找到更远的mx,因为mx更远可以获取更多信息,当然设置为p[i]更大为条件答案也对,不过会慢很多。
以上内容表示为代码:
if (mx > i) { if (mx - i >= p[j]) { p[i] = p[j]; } else { p[i] = mx - i; } } else { p[i] = 1; }
也即
p[i] = ( mx-i >= p[j] ) ? p[j] : mx - i;
至此就可以写出完整代码了。
#include <cstdio> #include <cstring> #include <string> #include <iostream> #include <algorithm> using namespace std; #define ll long long #define RE freopen("1.in","r",stdin); #define WE freopen("1.out","w",stdout); #define bug(x) cout<<#x<<":"<<(x)<<endl; const int maxn = 110000; int p[maxn * 2 + 5]; //记得*2 int Manacher(string s) { int ans = 2; //ans = max{p[i]},因为要-1,所以赋为2,回文最少1长度 int id = 1, mx = 1; //0号是$,没必要算 memset(p,0,sizeof(p)); for (int i = 1; i < s.length(); ++i) { int j = 2 * id - i; //对称点 if (mx > i) { //i在已探知范围内 if (mx - i >= p[j]) { //右边比较长,则对称性得知p[i]>=p[j] p[i] = p[j]; } else { //左边比较长,即j的回文超出了mx对称点,则i的回文至少到mx p[i] = mx - i; } } else { //未探知的就老老实实来 p[i] = 1; } //上面说明了p[i]>=某个值,具体多少得暴力继续 while (s[ i + p[i] ] == s[ i - p[i] ] ) { //有i+p[i]溢出,但不会有负下标溢出,不慌 p[i]++; } if (i + p[i] > mx) { //更新最右点和对应的id id = i; mx = id + p[id]; } ans = max(ans, p[i]); } return ans - 1; } int main() { // RE std::ios::sync_with_stdio(false); string str, s1; while (cin >> str) { s1 = "$#"; for (int i = 0; i < str.length(); ++i) { s1 += str[i]; s1 += "#"; } // cout << s1 << endl; cout << Manacher(s1) << endl; } return 0; }
无注释版
#include <cstdio> #include <cstring> #include <string> #include <iostream> #include <algorithm> using namespace std; #define RE freopen("1.in","r",stdin); #define WE freopen("1.out","w",stdout); #define bug(x) cout<<#x<<":"<<(x)<<endl; const int maxn = 110000; int p[maxn * 2 + 5]; int Manacher(string s) { int ans = 2; int id = 1, mx = 1; memset(p,0,sizeof(p)); for (int i = 1; i < s.length(); ++i) { int j = 2 * id - i; if (mx > i) { p[i] = ( mx-i >= p[j] ) ? p[j] : mx - i; } else { p[i] = 1; } while (s[ i + p[i] ] == s[ i - p[i] ] ) { p[i]++; } if (i + p[i] > mx) { id = i; mx = id + p[id]; } ans = max(ans, p[i]); } return ans - 1; } int main() { // std::ios::sync_with_stdio(false); string str, s1; while (cin >> str) { s1 = "$#"; for (int i = 0; i < str.length(); ++i) { s1 += str[i]; s1 += "#"; } cout << Manacher(s1) << endl; } return 0; }
练习地址:http://acm.hdu.edu.cn/showproblem.php?pid=3068
英文教程链接(里面图不错,虽然内容好像有点错的):http://articles.leetcode.com/longest-palindromic-substring-part-ii
相关文章推荐
- C++设计模式——Composite 组合模式
- php使用mysql_query查询超大结果集超内存的解决方法
- programming review (c++): (2)binary tree, BFS, DFS, recursive, non-recursive
- nyoj746 整数划分
- 【机房个人重构】数据库设计之逻辑模型
- codeforces 468B Two Sets
- Linux第三周——跟踪分析内核的启动过程
- [转载] Python 列表(list)、字典(dict)、字符串(string)常用基本操作小结
- 第一篇学习笔记
- 4.CocoaPods安装及使用教程
- 基因数据处理12之samtool的tview来查看sam的匹配文件
- leetcode258 Add Digits
- 向量空间模型
- Android控件第5类——ViewAnimator
- C++中const的问题(刷题后感)
- Gradle 修改 Maven 仓库地址
- iOS开发推送小结
- View圆角以及边框的设置
- linux下service+命令和直接去执行命令的区别,怎么自己建立一个service启动
- 多力的合成函数