马拉车算法manacher

manacher算法

1. 预处理解决奇回文和偶回文问题

比如 str = "bcbaa",在每个字符的开头,结尾和中间插入一个特殊字符“#”来得到一个新的字符串

“#b#c#b#a#a#”, 这样对于原来字符串中的奇回文“bcb”来说,在新的字符串中变成了“#b#c#b#”,还是奇回文,只是回文串长度从3变成了7;

image.png

注意代码中( i & 1) == 0,与1按位与,如果i是偶数则结果为0,否则为1,因为偶数的二进制表示为 *******0,最后一位为0,而1的二进制表示为00000001,按位与之后肯定全0,而奇数的二进制表示为*******1,与1按位与之后结果还是1。

这样处理过后,字符串中如果存在回文串,则所有回文串都是奇回文!

2. 三个辅助变量

str经过预处理之后记为charArr,对charArr中的每个字符都进行拓展,但不再是盲目的中心拓展,而是对每个字符拓展时,都会参考已经拓展过的结果。

数组pArr。其长度与charArr一样,pArr[i]的意义是以字符charArr[i]为回文中心时,扩出去得到的最大回文白半径是多少。

image.png

回文半径就是回文串长度整除2再加1,加1是因为算上了回文中心。

整数pR。意义是之前遍历的所有字符的所有回文子串中,最右边即将到达的位置。其实就是所有回文子串中,最右到达的字符位置再加1。

image.png

image.png

需要注意的是,当pR已经到了字符串的右边界的时候,就不用再更新了,因为不可能再有更右边的边界了。问题是,这个时候某个子串右边界已经到达了charArr的右边界了,再以右边的字符为中心进行拓展,其中心串长度也不能再增长了啊?还要继续往下算吗?

整数index。表示最近一次pR更新时,对应的回文中心的位置。每次更新pR就更新index,当pR不再更新,index也不再更新。

3. 从左到右一次计算pArr数组每个位置的值

得到的最大值就是charArr中最大回文半径,然后再把这个长度对应回str中的最大回文半径,然后即可求得最大回文子串

(1)假设现在计算到了以charArr[i]为中心的子串,在i位置之前的计算中,会不断更新pR(回文子串最后即将到达的位置)和index(最右即将到达位置的回文子串对应的回文中心)的值。分两种情况进行charArr[i]值的计算。

(2)pR-1位置即前边拓展过的回文子串中已经到达的最远位置,没有包含当前要计算的i位置。这个时候就是普通的中心拓展,没有获得加速。

image.png

(3)pR-1位置包含了当前的i位置。如下边图示

image.png

对于上边这个例子,其实不太恰当吧,pR为11的时候,以位置6-10为中心的回文子串长度已经不可能超过pR对应的那个子串的长度了,如果是要求最大回文串的话,则问题已经结束了吧。但是如果要求字符串中所有的回文串的个数,则问题还是要继续的。

  image.png

图中位置i是要计算回文半径的位置。右大是PR-1的位置,左大是右大以index对称的位置。则从左大到右大组成了一个回文串,称为大回文串。

已知i之前位置的pArr即回文半径都已经计算过了,以index为对称中心得到i对称的位置i',那么位置i'的回文半径也一定是计算过的。假设以i’为中心的最大回文串的左边界和右边界分别记为“左小”和“右小”,很明显左小处与右小处的字符相等。则根据左小右小与左大右大的位置关系可以分三种情况来讨论位置i处回文半径的计算。

分类标准是左小在左大的左边、右边,以及左小刚好等于左大三种情况!

注意左小到右小和左大到右大是两个回文串!

 

情况一:“左小”和“右小”完全包含在“左大”和“右大”内部,此时以位置i为中心的最大回文半径可直接确定,就是位置i'处的回文半径(已计算)

  image.png

a'是左小的前一个字符,b'是右小的后一个字符,因为左小到右小是以i'为中心的最大回文串,所以a' != b',否则以i'为中心的回文串会更大

把a',b',左小,右小以index为中心对称过去如图所示。因为左小到右小时回文串,右小'到左小'是它的逆序,当然也是回文串。而a'!=b',则a也不等于b,因此位置i处的最大回文半径与位置i'处一样,可直接确定。

 

情况二:左小到右小这个字符串的左侧部分在左大到右大字符串的外部,即小字符串左小左大在左边超出左大这个边界了,此时位置i处回文半径可直接确定。

  image.png

注意这里的abcd跟情况一不是一个含义,情况一中a'是左小的前一个字符,b'是右小的后一个字符,而情况二中的ab与左小右小没有关系,只与大字符串左大到右大有关系。

a是左大左边第一个字符,左大’是左大以i'为中心的对称位置。同理右大'是右大以i为中心的对称位置。

b是左大’右边第一个字符,c是右大’左边第一个字符,d是右大右边第一个字符。

因为左大到左大’包含在左小到右小之间,而左小到右小是回文串,因此左大到左大’也是回文串,因此右大’到右大也是回文串。

则右大’到右大就是位置i对应的最大回文串。

证明:

因为a和b都包含在左小到右小之间,所以a == b成立

又因为b和c关于index对称,包含在左大到右大之间,所以b==c成立

又因为index处的最长回文串就是左大到右大,所以a != d,否则index处的回文串会更长

综上,c != d

所以右大’到右大就是位置i处的最长回文串

 

情况三:左小和左大是同一个位置,以i'为中心的最大回文串刚好压在以index为中心的最大回文串的边界上,此时只能在中心拓展时减少运算,不能直接确定位置i处的回文半径。

  image.png

如图,左小左大重叠,右大’与右大与左小左大对称,则以位置i为中心的回文串至少是右大’到右大

设a为右大’左侧第一个字符,b为右大右侧第一个字符,关于a和b是否相等我们是无法判断的,因此要从右大'左侧和右大的右侧字符分配向左向右进行拓展。才能最终确定位置i处的最大回文半径。

4. 得到数组pArr[i],求最大回文串长度

设位置i处回文半径最大,pArr[i]为max,则max - 1就是原字符串str中最长回文子串。

设在字符串charArr中的最长回文串长度为m,则 max = m/2 +1,这里的出发是整除,加1是加上中心字符。

设此最长回文串对应str中回文串长度为n,则m = 2n+1

故 (2n+1)/2 + 1 = max 这里得除法也是整除

得 n =max -1

进阶问题一 求字符串中所有回文子串的个数

注意,如果一个位置i处的最大回文半径是x,则这个位置i处的所有回文子串的个数就是x,所以如果要求一个字符串中所有的回文子串,则把pArr数组中所有元素加起来即可。

进阶问题二 在字符串最后添加最少字符,使整个字符串成为回文串

在必须包含最后一个字符的情况下,查找最长的回文子串,然后把字符串前边不在这个回文子串内的子字符串逆序接在后边,就能使得整个字符串成为回文串。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值