Manacher算法求回文子串

这篇文章对Manacher介绍得很详细,而且很容易读懂,原文转自:http://blog.csdn.net/pi9nc/article/details/9251455

一、问题描述

现给定一个已知的字符串str[],现在想要在O(n)的时间复杂度之内求出一个最长的回文子字符串(正着和倒着顺序读一致)。

Manacher最早发现了可以用O(n)的时间复杂度来解决该问题,所以这种方法称之为Manacher算法。

二、符号说明

回文串包括奇数长的和偶数长的,一般求的时候都要分情况讨论,Manacher的这个算法做了个简单的处理,把奇偶情况统一了,为了避免索引数超出数组边界值做字符比较,可以在处理过的字符串的第一个位置(索引为0的位置)加入一个区分字符,并在这个字符串的最后加入结束标记‘\0‘,处理后的字符串形式如最后所举的例子。P[]存放的是回文子串的半径。现在需要计算P[i]的值,那么id代表了索引号i之前的那个使得回文子串最右面的字符的索引号最大的索引号的值,用符号表示就是:

其中,argmax求得的是索引下标的值,另外引入符号mx,j=2*id-i;

三、算法步骤

下面进入更新P[]的步骤:

第一步,先初始化P[0]=0。

第二步,当i<strlen(str)时执行第三步,否则结束

第三步,判断P[id]+id=mx>i的值,如果为假执行第四步。否则执行第五步。

第四步,初始化P[i]=0,并且执行while(str[i+P[i]+1] == str[i-P[i]-1]) ++P[i];i++,得到所求的P[i]。

第五步,如果mx-i>P[j],则执行第六步,否则执行第七步。

第六步,P[i]=P[j],这时已经求出P[i],回到第二步。

第七步,这时P[i]>=mx-i,初始化P[i]=mx-i,并执行while(str[i+P[i]+1] == str[i-P[i]-1]) ++P[i];i++,得到所求的P[i],回到第二步。

下面是根据P[]找出最长回文子串的步骤:

第一步,找到P[]中的最大值,并记录最大的P[]的值以及其索引编号。

第二步,如果回文字子串的起始位置为‘#‘的话,最大的回文字子串的长度减1。

第三步,得出最长回文子串的一个字符在原字符串中的索引位置。

第四步,根据最长回文子串的起始索引位置编号和最大长度得出最长回文子串。

四、算法原理

下面来解释一下更新P[]的的第三步,第六步和第七步,至于其他的步骤都是非常号理解的。

首先对第三步进行说明,当P[id]+id=mx>i时说明以i为中心点可能存在回文子串,这时就可以将P[i]初始化成该回文子串的值在进行扩展搜索回文子串的半径是否能够增大,省去了P[i]从0开始搜索的一些步骤。

对第六步说明,即对mx-i>P[j]的情形进行说明。这时的字符串可以表示成下图:

图中最下面的红色线条是之前求得的索引号i之前的那个使得回文子串最右面的字符的索引号最大的那个回文子字符串。j点是i关于id的对称点,由于红的字符串是回文字符串,所以关于j对称的回文子串和关于i对称的回文子串是完全一样的(图中两段绿色的线条),而满足mx-i>P[j]时说明此时j的回文子串半径小于j到mx关于j对称的左端点的差,此时可以初始化P[i]=P[j]。

对第七步说明,即对mx-i<=P[j]的情形进行说明。这时的字符串可以表示成下图:

图中最下面的红色线条仍然是之前求得的索引号i之前的那个使得回文子串最右面的字符的索引号最大的那个回文子字符串。j点是i关于id的对称点,由于红的字符串是回文字符串,所以关于j对称的回文子串和关于i对称的在mx和mx的对称点之间的回文子串是完全一样的(图中两段绿色的线条),而满足mx-i<=P[j]时说明此时j的回文子串半径大于或等于j到mx关于j对称的左端点的差,此时可以初始化P[i]=mx-i,再对P[i]的回文子串半径进行进一步的增大。

根据P[]找出最长回文子串的步骤的一点说明。

如果找到转换后字符串的最长回文子串第一个字符为‘#’,则需要对最大长度减1。另外,转换后的字符串的回文子串的半径(即P[]的值)加1就是原字符串中回文子串的长度(包含的任何一个回文子串都有这个关系)。

五、算法举例

为了更好的理解算法的原理,我在这里给出一个例子。

str_s = a b a a b a;
Index = 0 1 2 3 4 5 6 7 8 9 10 11 12
str[] = @ a # b # a # a # b #  a  ‘\0‘;
P[]   = 0 0 0 2 0 1 5 1 0 2 0  0;

其中最上面一行的Index是序号,str[]存的是要查找的字符串。P[]存放的是回文子串的半径。如,Index为3处的P[3]=2代表的意思是str[3]的左数2个和右数两个字符构成一个回文子串即a#b#a。下面按照算法的步骤一步一步来算这个P[]的值。

首先是初始化P[0]=0;容易得出P[1]=0;计算P[2],id=1,mx=1<2,故初始化P[2]=0,接下来匹配是否有回文字符串,即依次对P[2]向两侧展开,检查P[2]是否有相等的字符,可以得出P[2]还是为0;计算P[3],id=2,mx=2<3,初始化P[3]=0,同样检查P[3]的两侧,计算出P[3]=2;接下来计算P[4],id=3,mx=5>4,这是可以得出P[4]>=min(P[2*3-4],5-4),即这时P[4]的最小值为0,这时5-4>P[2*3-4],故P[4]=P[2*3-4]=0;计算P[5],id=3,mx=5<=5,故初始化P[5]=0,检查P[5]的两侧,这时计算出P[5]=1;计算P[6]的情况同P[5],得到P[6]=5;接下来计算P[7],id=6,mx=P[6]+6=11>7,P[7]=min(P[2*6-7],11-7)=1,即这时P[6]>=1,此时mx-i=11-7=4>=P[2*6-7],故P[7]=P[2*6-7]=1;计算P[8],id=6,mx=P[6]+6=11>8,P[8]=min(p[2*6-8],11-8)=0,初始化P[8]=0,检查P[8]的两侧,这时计算出P[8]=0;计算P[9],id=6,mx=P[6]+6=11>9,P[9]=min(P[2*6-9],11-9)=0,初始化P[9]=0,检查P[9]的两侧,这时计算出P[9]=2;后面的做相似处理可以算出所有的P[i]的值。

上面的例子可能不太合适,没有出现mx-i<=P[2*id-i]的情况,不过出现这种情况的话也按照算法的步骤分析一下即可。出现这个情况的例子我再找找试试。

时间: 2024-10-08 01:19:11

Manacher算法求回文子串的相关文章

HDU 5371(2015多校7)-Hotaru&#39;s problem(Manacher算法求回文串)

题目地址:HDU 5371 题意:给你一个具有n个元素的整数序列,问你是否存在这样一个子序列,该子序列分为三部分,第一部分与第三部分相同,第一部分与第二部分对称,如果存在求最长的符合这种条件的序列. 思路:用Manacher算法来处理回文串的长度,记录下以每一个-1(Manacher算法的插入)为中心的最大回文串的长度.然后从最大的开始穷举,只要p[i]-1即能得出以数字为中心的最大回文串的长度,然后找到右边对应的'-1',判断p[i]是不是大于所穷举的长度,如果当前的满足三段,那么就跳出,继续

马拉车算法——求回文子串个数zoj4110

zoj的测评姬好能卡时间.. 求回文子串的个数:只要把p[i]/2就行了: 如果s_new[i]是‘#’,算的是没有中心的偶回文串 反之是奇回文串 /* 给定两个字符串s,t 结论:s,t不相同的第一个字符下标为l,最后一个字符下标为r 如果l==r,那么不存在解 如果l<r,那么翻转s的一个子串时,一定是将s[l]和s[r]互相翻转 反证:假设存在s[l]和s[r+k]的翻转方法, 因为s[r+k]=t[r+k]=t[l],且s[l]=t[r+k],可得s[l]=t[l],矛盾 l-k同理 所

hdu5340—Three Palindromes—(Manacher算法)——回文子串

Three Palindromes Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)Total Submission(s): 1948    Accepted Submission(s): 687 Problem Description Can we divided a given string S into three nonempty palindromes? Input F

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这个人发明了一种新算法 他是这样想的 假如我先前已经找到的回

HDU 3616 Best Reward (Manacher算法 前缀回文+后缀回文)

Best Reward Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others) Total Submission(s): 785    Accepted Submission(s): 338 Problem Description After an uphill battle, General Li won a great victory. Now the head of state

O(n) 求最长回文子串的 Manacher 算法

Manacher是一个可以在O(n)的时间内求出一个长度为n的字符串的算法. 以为回文子串有偶数长度,也有奇数长度,分别处理会很不方便. 所以在每两个字符中间插入一个无关字符,如‘#’,这样所有的回文子串都变为奇数长度. 两端在添加不同的无关字符防止匹配时越界. 如: abba 变成 $#a#b#b#a#& 预处理代码: void Prepare() { l = strlen(Str); S[0] = '$'; for (int i = 0; i <= l - 1; i++) { S[(i

最长回文子串(Manacher算法模板题)&amp;&amp;对称字符串问题

manacher:可以解决最长回文问题. 算法:1.首先,将字符串的每个字符左右加入#,并在s0位置加入*(如果字符串中本身含有这些,则换成未出现过的字符),此时字符串的长度为len+len+3,即加入了len+1个#和一个*; (比如:aba变成 *#a#b#a#) 2.得到一个p数组,该数组是基于新字符串进行的. 得到p数组 :①从1~2*len遍历字符串,即从第一个#到最后一个字符(或者说*和最后一个#不用),即要得到p[1]~p[2*len]: ②遍历的过程中,定义了两个新变量,id和m