MANACHER最长回文算法

博客已经搬家,请前往http://gqqnbig.me/ 阅读格式良好的文章。

本文将一步一步构造Manacher算法,心急的一定看不懂!请先练习下面的习题。

探索最长回文串性质

题1:已知字符串以center为中心对称,求完整的字符串。

abcd???
   |
center

abcdcba
   |
center

题2:接上题,abcdcba后面还有一些字符,以center2为中心,最大对称半径[ref]半径大于等于1。[/ref]为7,求完整的字符串。

答根据center2的对称性质,可以知道字符串为

abcdcba???abcdcb??

又根据center2最大对称半径为7,而不是8,所以c后面一定不是b(标识为b)。center3的对称半径确定!而且,center关于center2的对称点center3的索引号为center2*2-center=8*2-3=13。对称半径为center2+7-center3=8+7-13=2。

题3:稍微调整上题,若center2在a,对称半径为8,求完整的字符串和center3的对称半径。

答:字符串为

babcdcbabcdcbab?

center3=center2*2-center=7*2-4=10。对称半径为center2+8-center3=7+8-10=5。这样对吗?

看起来半径不正确!应该同center一样都是4。问题出在哪里呢?明明用的是跟题2一样的公式啊!因为题2跟题3情况不同。题2 center左边超出了center2的对称范围;而题3 center左边没超出center2的对称范围。center3的半径确定。题4:同样,center在x,半径为2,center2在a,半径为5。求完整的字符串和center3半径。

答:根据center2的对称性补全center2范围内的字符。又因为center2的对称半径为5而不是6,索引11位的字符不是c。

ccdxdbab7dxdc11?12????

所以center3的半径就是2了吗?不一定哦!因为我们只知道索引11位的字符不是c,但它可能是b,这样就与索引7位的b匹配,对称半径达到3。同样地,我们也不知道索引12位的字符是什么。所以,在center和center2的左边界重合时,我们只知道center3半径的最小值。根据这三道例题,我们已经总结出回文字符的半径性质:

  1. 当center1范围左超center2范围(简称左超),center3的半径可由计算得出,为确切值。
  2. 当center1范围内含于center2范围(简称内含),center3的半径等于其关于center2的对称点半径,为确切值。
  3. 当center1左边界与center2左边界重合(简称接边),center2半径大于等于其关于center2的对称点半径,值不确定。

在情况1、2中,求的是确切值,所以即使center3内含于多个对称范围,每个对称范围求出的半径都一定是一样的。在情况3中,所有对称范围的计算值的最大值是center3半径的最小值。

基本代码

下面给出基本代码。[code lang="java"]public static int getPalindromeLength(String s){if (s.length() == 0)return 0;StringBuilder sb = new StringBuilder(s.length() * 2 + 1);sb.setLength(sb.capacity());for (int i = 0; i < s.length(); i++)sb.setCharAt(i * 2 + 1, s.charAt(i));s
= sb.toString();int[] radii = new int[s.length()];for (int center = 0; center < s.length() - 1; center++) // O(n){boolean notSure = true;// 检查center是否在某个对称区域的右半边for (int center2 = center - 1; center2 >= center / 2; center2--){if (center2 + radii[center2] >
center) // center在以center2为中心的对称区域的右半边{int c1 = center2 * 2 - center;assert c1 >= 0;if (c1 - radii[c1] < center2 - radii[center2])// 左超,半径确定{radii[center] = center2 + radii[center2] - center;notSure = false;break;}else if (c1 - radii[c1] > center2 - radii[center2])
// 内含,半径确定{radii[center] = radii[c1];notSure = false;break;}else// 接边,半径可能变化radii[center] = Math.max(radii[c1], radii[center]);}}if (notSure){// ccxbabxbabxcc// 0123456789012/* * 索引6的x关于索引4的a对称,根据索引2的x得对称半径1。 但实际上索引6的x是整个字符串的对称中心。 */int r = radii[center];while
(center - r >= 0 && center + r < s.length() && s.charAt(center - r) == readChar(s, center + r))r++;radii[center] = r;}}int maxRadius = radii[0];for (int i = 0; i < radii.length; i++)maxRadius = Math.max(maxRadius, radii[i]);return (maxRadius - 1);}private
static char readChar(String s, int index)//这个方法可用来查看计算复杂度{System.out.println("读取字符" + index);return s.charAt(index);}[/code]

现在分析复杂度,令比较运算为耗时间的操作。该代码通过notSure变量,仅当i关于center2的对称点接边的时候才读取字符来比较。前面性质分析中说的是左侧接边,根据对称性也是右侧接边。进入if (notSure)分支后继续读取更右边的字符,然后记录在radii里。以后,从radii里读就可以了。建议亲手运行代码看看,会发现程序不会重读前面读过的字符,不会读了012345,然后又读2345。既然程序只读取字符2n次,那么比较进行了n次,所以时间复杂度是O(n)。网上很多代码与上面的代码不太一样,有id、mx什么的,尤其有一个变量记录什么最右边的位置,不太好懂。读者如果理解了上面的基本代码,那么可以再看下下面的变形代码,变形代码与网上的代码比较像。变形代码的复杂度同样是O(n)。[code
lang="java" title= language=]public static int getPalindromeLength2(String s){if (s.length() == 0)return 0;StringBuilder sb = new StringBuilder(s.length() * 2 + 1);sb.setLength(sb.capacity());for (int i = 0; i < s.length(); i++)sb.setCharAt(i * 2 + 1, s.charAt(i));s
= sb.toString();int[] radii = new int[s.length()];int rCenter2 = 0;// 右边界最大的cfor (int i = 0; i < s.length() - 1; i++) // O(n){// 检查center是否在某个对称区域的右半边if (rCenter2 + radii[rCenter2] > i) // i在以center2为中心的对称区域的右半边{int c1 = rCenter2 * 2 - i;assert c1 >= 0;if
(c1 - radii[c1] < rCenter2 - radii[rCenter2])// 左超,半径确定radii[i] = rCenter2 + radii[rCenter2] - i;else// 不左超。但如果接边,半径可能变化radii[i] = radii[c1];}// 如果右最大的c都不包括i,那么其他c更不会包括了。if (i + radii[i] == rCenter2 + radii[rCenter2])// 接边{int r = radii[i];while (i - r >=
0 && i + r < s.length() && s.charAt(i - r) == readChar(s, i + r))r++;radii[i] = r;// 接边后超右边比较,半径可能更大!if (i + radii[i] > rCenter2 + radii[rCenter2])rCenter2 = i;}}int maxRadius = radii[0];for (int i = 0; i < radii.length; i++)maxRadius = Math.max(maxRadius,
radii[i]);return (maxRadius - 1);}[/code]

参考资料

[CiteWeb author="xiangzhai" url="https://github.com/xiangzhai/leetcode/blob/master/question/longest-palindromic-substring-part-ii.md" title="最长回文子字符串 第二部" publisher="" date="2014-02-14" accessdate="2015-02-06"]

时间: 2024-10-24 01:17:24

MANACHER最长回文算法的相关文章

Hdu 3294 Girls&#39; research (manacher 最长回文串)

题目链接: Hdu 3294  Girls' research 题目描述: 给出一串字符串代表暗码,暗码字符是通过明码循环移位得到的,比如给定b,就有b == a,c == b,d == c,.......,a == z. 问最长回文串所在区间,以及最长回文串所表示的明码. 解题思路: 字符串长度[1,200000],用manacher算法很轻松就搞定了. get√新技能请点击me 1 #include <cstdio> 2 #include <cstring> 3 #includ

hdu 3068 最长回文(manacher&amp;最长回文子串)

最长回文 Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submission(s): 7317    Accepted Submission(s): 2500 Problem Description 给出一个只由小写英文字符a,b,c...y,z组成的字符串S,求S中最长回文串的长度. 回文就是正反读都是一样的字符串,如aba, abba等 Input 输入有多组

(字符串) Manacher 最长回文子串。

最长回文子串就是一个字符串的一个子串,他从左往右读和从右往左读是一样的. 可以用 Manacher 算法来求,他的复杂度是 O(n) . 可以看这篇文章 http://blog.csdn.net/ywhorizen/article/details/6629268 但是其中应该有一个错误(纠结了我一天...) 就是 这一句,文章里面是说当 Mx-i <= Mp[j] 的时候就要用到,因为后面的还没比较. 但是如图,蓝色的部分是相等的,如果红色的等于蓝色的话,那么Mx就不可能是这个值,所以红色的一定

[Manacher]最长回文子串

很久没有写博客了 啪啪啪 写一些东西吧 最长回文子串怎么求呢 首先我们得知道什么是子串,给你一个长长的串,里面任意连续的一段就是它的子串,当然一个字符也是子串 接着什么是回文串呢 不好描述 但是看例子很容易懂:aba 121 1221 1 然后我们有一种很显然的寻找方法 当然是枚举中点 然后尽可能的往外扩大回文串的长度 这种算法花费的时间是N(字符串长度)*Mk(可扩展长度)Mk<N 当然这样的时间并不是很让人满意 于是Manacher这个人发明了一种新算法 他是这样想的 假如我先前已经找到的回

最长回文算法2

问题: 求给定输入字符串的最长回文子序列(子序列不要求连续). 用LPS(i,j)表示从字符串第i个字符到第j个字符的最长回文子序列的长度,字符串的长度为n,则要求LPS(1,n),则: LPS(i,j)=0; i>j; LPS(i,j)=1; i==j; LPS(i,j)=LPS(i+1,j-1)+2; str[i]==str[j]; LPS(i,j)=max(LPS(i+1,j),LPS(i,j-1)); str[i]!=str[j]; // zuichanghuiwen2.cpp : 定义

hdu----(4513)吉哥系列故事——完美队形II(manacher(最长回文串算法))

吉哥系列故事——完美队形II Time Limit: 3000/1000 MS (Java/Others)    Memory Limit: 65535/32768 K (Java/Others)Total Submission(s): 1012    Accepted Submission(s): 358 Problem Description 吉哥又想出了一个新的完美队形游戏! 假设有n个人按顺序站在他的面前,他们的身高分别是h[1], h[2] ... h[n],吉哥希望从中挑出一些人,让

Manacher 最长回文串问题

有人问你,一个字符串中最长的回文字串是谁? 作为一个 2B 青年, 一年前的我会这样回答:暴力枚举每个字串,判断合法与否? ╮(╯▽╰)╭ 就是没什么戏.... 后来,在机房的一个大牛的介绍下,我知道了 Manacher 算法. ............................ 其实 Manacher 和 KMP 是非常之像的. 令 rad[i] 表示以 第 i 个字符为中心的回文字串的半径长度. 那么我们考虑一下当前最远拓展到 j 编号为 rad[k],那么我们当前要计算的 rad[i

HDU 3068 最长回文 (manacher算法)

最长回文 Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submission(s): 9188    Accepted Submission(s): 3159 Problem Description 给出一个只由小写英文字符a,b,c...y,z组成的字符串S,求S中最长回文串的长度. 回文就是正反读都是一样的字符串,如aba, abba等 Input 输入有多组

最长回文---hdu3068 (回文串 manacher 算法模板)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3068 题意很清楚:就是求一个串s的子串中最长回文串的长度:这类题用到了manacher算法 manacher算法(复制大神的解释): 定义数组p[i]表示以i为中心的(包含i这个字符)回文串半径长 将字符串s从前扫到后for(int i=0;i<strlen(s);++i)来计算p[i],则最大的p[i]就是最长回文串长度,则问题是如何去求p[i]? 由于s是从前扫到后的,所以需要计算p[i]时一定