manacher算法
1. 预处理解决奇回文和偶回文问题
比如 str = "bcbaa",在每个字符的开头,结尾和中间插入一个特殊字符“#”来得到一个新的字符串
“#b#c#b#a#a#”, 这样对于原来字符串中的奇回文“bcb”来说,在新的字符串中变成了“#b#c#b#”,还是奇回文,只是回文串长度从3变成了7;
注意代码中( 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]为回文中心时,扩出去得到的最大回文白半径是多少。
回文半径就是回文串长度整除2再加1,加1是因为算上了回文中心。
整数pR。意义是之前遍历的所有字符的所有回文子串中,最右边即将到达的位置。其实就是所有回文子串中,最右到达的字符位置再加1。
需要注意的是,当pR已经到了字符串的右边界的时候,就不用再更新了,因为不可能再有更右边的边界了。问题是,这个时候某个子串右边界已经到达了charArr的右边界了,再以右边的字符为中心进行拓展,其中心串长度也不能再增长了啊?还要继续往下算吗?
整数index。表示最近一次pR更新时,对应的回文中心的位置。每次更新pR就更新index,当pR不再更新,index也不再更新。
3. 从左到右一次计算pArr数组每个位置的值
得到的最大值就是charArr中最大回文半径,然后再把这个长度对应回str中的最大回文半径,然后即可求得最大回文子串
(1)假设现在计算到了以charArr[i]为中心的子串,在i位置之前的计算中,会不断更新pR(回文子串最后即将到达的位置)和index(最右即将到达位置的回文子串对应的回文中心)的值。分两种情况进行charArr[i]值的计算。
(2)pR-1位置即前边拓展过的回文子串中已经到达的最远位置,没有包含当前要计算的i位置。这个时候就是普通的中心拓展,没有获得加速。
(3)pR-1位置包含了当前的i位置。如下边图示
对于上边这个例子,其实不太恰当吧,pR为11的时候,以位置6-10为中心的回文子串长度已经不可能超过pR对应的那个子串的长度了,如果是要求最大回文串的话,则问题已经结束了吧。但是如果要求字符串中所有的回文串的个数,则问题还是要继续的。
图中位置i是要计算回文半径的位置。右大是PR-1的位置,左大是右大以index对称的位置。则从左大到右大组成了一个回文串,称为大回文串。
已知i之前位置的pArr即回文半径都已经计算过了,以index为对称中心得到i对称的位置i',那么位置i'的回文半径也一定是计算过的。假设以i’为中心的最大回文串的左边界和右边界分别记为“左小”和“右小”,很明显左小处与右小处的字符相等。则根据左小右小与左大右大的位置关系可以分三种情况来讨论位置i处回文半径的计算。
分类标准是左小在左大的左边、右边,以及左小刚好等于左大三种情况!
注意左小到右小和左大到右大是两个回文串!
情况一:“左小”和“右小”完全包含在“左大”和“右大”内部,此时以位置i为中心的最大回文半径可直接确定,就是位置i'处的回文半径(已计算)
a'是左小的前一个字符,b'是右小的后一个字符,因为左小到右小是以i'为中心的最大回文串,所以a' != b',否则以i'为中心的回文串会更大
把a',b',左小,右小以index为中心对称过去如图所示。因为左小到右小时回文串,右小'到左小'是它的逆序,当然也是回文串。而a'!=b',则a也不等于b,因此位置i处的最大回文半径与位置i'处一样,可直接确定。
情况二:左小到右小这个字符串的左侧部分在左大到右大字符串的外部,即小字符串左小左大在左边超出左大这个边界了,此时位置i处回文半径可直接确定。
注意这里的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处的回文半径。
如图,左小左大重叠,右大’与右大与左小左大对称,则以位置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数组中所有元素加起来即可。
进阶问题二 在字符串最后添加最少字符,使整个字符串成为回文串
在必须包含最后一个字符的情况下,查找最长的回文子串,然后把字符串前边不在这个回文子串内的子字符串逆序接在后边,就能使得整个字符串成为回文串。