java manacher算法计算最长回文字符串

来源:互联网 发布:linux上jsp页面跳转 编辑:程序博客网 时间:2024/05/16 19:20

求解最长回文字符串朴素算法


最朴素的算法是暴力解法就不谈了,时间复杂度是O(n3)。

比最朴素解法稍微好一些的解法是O(n2)的一种解法,思路是从对称轴开始考虑,根据回文字符串长度的奇偶分为两种情况,如果最长的回文字符串为偶数位,那么他的对称轴是中间部分,如果是奇数位则为中间的一个字符。那么从该字符串的第一个字符开始一共有n+(n-1)个对称轴,每个对称轴都需要便利一遍n来找最长的回文字符串,所以算法复杂度是O(n2)。

普通的情况下,在没有听说过其他解法的时候我们最好的处理就是这种方法了,下面介绍一种算法复杂度为O(n)的算法,manacher算法。

manacher算法解决最长回文字符串问题


刚才的第二种解法中我们不断的更改对称轴并进行匹配。在这个过程中我们貌似进行了很多重复的工作,比如相邻的两个对称轴我们检测的回文字符串会重复的不断检测这两个字符旁边的字符。算法之所以能够优化的根本原因就是把重复计算去掉了,那么我们怎么样才能减少相近字符的重复计算呢?

str1 : abacbcac

str2 : #a#b#a#c#b#c#a#c#

我们要计算的就是str1的最长回文字符串,manacher算法的思路是把字符串包括前后的所有空隙都插入一个相同的其他字符,就像例子中我们构造出了str2字符串。无论原字符串是奇还是偶构造出来的结果都是一个奇数位的字符串。

回文半径

为了方便说明算法,我们引入回文半径的概念,回文半径是指在str2中每个字符(每个字符不包括空隙,但是包括#号)的回文序列从对称轴到最远端的长度(包含自身)。那么对于str2中的每个字符我们都可以计算他的最长回文半径。

s t r 2:#a#b#a#c#b#c#a#c#

半径 :12131212161212121

我们通过肉眼计算得到了回文半径,那么接下来有个很重要的定理:

str1中最长的回文字符串长度是str2中最大回文半径-1.

我们观察str1中的最大回文字符串为acbca为5,str2中的最大回文半径是6,刚好符合上面的定理。说实话,这个其中的具体原因是什么,这里不解释,因为这个算法被创造出来是一个数学的过程,但是我们主要要去使用这个算法,如果你在面试的时候只有一支笔一张纸,没听说过这个算法你是不可能在短时间自己证明出它的,我们要做的就是熟练运用。

如何求解回文半径?


问题简单了,我们开始要求回文字符串,现在只需要计算回文半径就可以了,那么如何用最低的复杂度计算回文半径呢?

首先我们看最简单的方法,我们只需要求所有的回文半径,每个字符进行便利即可,最多需要O(n2)的复杂度,这听起来和最开始的方法的复杂度很像,那么现在的计算和刚开始的计算有什么区别呢?开始的计算方法要计算空隙和字符所有的回文情况,现在只需要计算每个字符的回文半径即可,不需要计算空隙,那么相比较之前的方法计算回文半径会出现很多重复次的计算。

只要我们能够避免重复计算,那么我们就可以获得更小的复杂度计算方法。

如何避免重复计算呢?我们从str2字符串首部开始,将第一个点标记为pos,将该点右侧最长半径处的边缘点记作MaxR,表示右侧最大值点,因为我们知道字符串具有对称性,所以所有在pos和MaxR之间的字符i都有两种情况,第一种是该字符i的半径和pos左侧相应位置的j点的半径长相同;第二种情况是i半径长度比j还大,i的半径可以扩展到MaxR的右侧,那么这个时候我们进行手动遍历以i为对称轴的对称的两点来计算i的最大半径,并在这之后更新i为新的pos,i的右侧为新的MaxR。除此之外还有一种情况就是i的点在MaxR右侧,此时我们正常以i为对称轴进行左右对称判断更新i的半径,但是无论结果如何都更新i变为pos,i的右侧为MaxR。

算法如上,我简单的总结就是,如果i在pos和MaxR中间我们有可能通过pos的对称点快速计算出i的半径,否则进行朴素的对称计算,但是因为我们采用的这种标记最右侧MaxR的方法,所以我们会避免很多重复计算,时间复杂度为O(n+m)考虑到m是常数,复杂度为O(n)。