利用Manacher算法寻找字符串中的最长回文序列(palindrome)

寻找字符串中的最长回文序列和所有回文序列(正向和反向一样的序列,如aba,abba等)算是挺早以前提出的算法问题了,最近再刷Leetcode算法题的时候遇到了一个(题目),所以就顺便写下。
如果用正反向遍历的方法的话时间复杂度将会是O(N^2),而利用Manacher算法将会是O(N),在处理长序列的时候能显著提高速度。

算法原理

  1. 回文序列的左右是对称的,也就是说在找到一个回文序列的时候,回文序列的右半部份将会是左半部分的镜像,在符合一定条件的时候可以直接判断以当前字符为中心的回文序列的长度
    以下图为例(途中的#是在编程过程中为了将原来的偶数长度的回文序列转化为奇数长度,简化代码)
  2. 从左边开始分析,上一次寻找倒的的回文序列的中心记为C,当前位置记为R,与之通过C镜像相对的位置记为L。如果R加上通过L获知的当前位置可能的回文序列半径之后的位置还处于C的回文序列范围内,那么R位置的回文序列长度就与L处相等,最长回文序列的中心仍为C。记录当前位置为中心的回文序列半径(P)。
  3. 如果上述条件不成立,那么R将成为新的回文序列中心,这时以R为中心向两边同时延伸,直到遇到不同字符位置,获取此时以R为中心的回文序列长度。记录(P)
  4. 重复上述过程直至最后一个字符
  5. 获取最长半径所对应的字符坐标,即可得到最长的回文序列
    PS:如果只需要直到最长序列,那么更新C只在当前最标下的回文序列半径之前最长的还要长的时候才进行,这样操作的话能直接获得最长回文序列中心坐标。

实现代码

class Solution(object):
    def longestPalindrome(self, s):
        """
        :type s: str
        :rtype: str
        """
        center = 0
        # insert dollar signs between each character
        s = '$' + '$'.join(list(s)) + '$'
        radius_lengths = [1] * len(s)
        for index, _ in enumerate(s):
            if center + radius_lengths[center] > index+radius_lengths[index]:
                radius_lengths[index] = radius_lengths[2*center-index]
            while index-radius_lengths[index] >= 0 and index+radius_lengths[index] < len(s) and s[index-radius_lengths[index]] == s[index+radius_lengths[index]]:
                radius_lengths[index] += 1
            if radius_lengths[center] < radius_lengths[index]:
                center = index
        pali = s[center-radius_lengths[center]+2:center+radius_lengths[center]:2]
        return pali
obj = Solution()
s = "abb"
print obj.longestPalindrome(s)

当前将会输出bb。

参考资料

  1. https://en.wikipedia.org/wiki/Longest_palindromic_substring
  2. http://articles.leetcode.com/longest-palindromic-substring-part-ii/
  3. http://www.geeksforgeeks.org/manachers-algorithm-linear-time-longest-palindromic-substring-part-1/
posted on 2016-09-26 21:03  Arkenstone  阅读(1701)  评论(0编辑  收藏  举报