1. 程式人生 > >LeetCode 5. Longest Palindromic Substring 最長迴文子串 Python 四種解法(Manacher 動態規劃)

LeetCode 5. Longest Palindromic Substring 最長迴文子串 Python 四種解法(Manacher 動態規劃)

Longest Palindromic Substring 最長迴文子串 學習筆記

1. Brute method

第一種方法:直接迴圈求解,o(n2)o(n2)

class Solution:
    def longestPalindrome(self, s):
        """
        :type s: str
        :rtype: str
        """
        l = len(s)
        max_length = 0
        palindromic = ''
        if len(s) == 1:
            return
s for i in range(l): for j in range(i + 1, l): is_palindromic = True for k in range(i, int((i + j) / 2) + 1): if s[k] != s[j - k + i]: is_palindromic = False break if
is_palindromic and (j - i + 1) > max_length: max_length = j - i + 1 palindromic = s[i:j + 1] if palindromic == '': palindromic = s[0] return palindromic

2. 中心列舉

通過列舉字串子串的中心而不是起點,向兩邊同時擴散,依然是逐一判斷子串的迴文性。這種優化演算法比之前第一種演算法在最壞的情況下(即只有一種字元的字串)效率會有很大程度的上升。

class Solution:
    def longestPalindrome(self, s):
        """
        :type s: str
        :rtype: str
        """
        l = len(s)
        max_length = 0
        palindromic = ''
        for i in range(l):
            x = 1
            while (i - x) >= 0 and (i + x) < l:
                if s[i + x] == s[i - x]:
                    x += 1
                else:
                    break
            x -= 1
            if 2 * x + 1 > max_length:
                max_length = 2 * x + 1
                palindromic = s[i - x:i + x + 1]
            x = 0
            if (i + 1) < l:
                while (i - x) >= 0 and (i + 1 + x) < l:
                    if s[i + 1 + x] == s[i - x]:
                        x += 1
                    else:
                        break
            x -= 1
            if 2 * x + 2 > max_length:
                max_length = 2 * x + 2
                palindromic = s[i - x:i + x + 2]
        if palindromic == '':
            palindromic = s[0]
        return palindromic

3. Manacher’s Algorithm

複雜度o(n)o(n)
有兩個主要的步驟:

  1. 將所有可能的奇數/偶數長度的迴文子串都轉換成了奇數長度:在每個字元的兩邊都插入一個特殊的符號。abba => #a#b#b#a#, aba => #a#b#a#
  2. 用陣列 P[i] 來記錄以字元S[i]為中心的最長迴文子串向左/右擴張的長度,並增加兩個輔助變數id和mx,其中 id 為已知的 {右邊界最大} 的迴文子串的中心,mx則為id+P[id],也就是這個子串的右邊界。
    mxi>p[j]:p[i]=p[j]mx−i>p[j]:p[i]=p[j]

# manacher演算法
def manacher(self):
        s = '#' + '#'.join(self.string) + '#'               # 字串處理,用特殊字元隔離字串,方便處理偶數子串
        lens = len(s)
        f = []                                          # 輔助列表:f[i]表示i作中心的最長迴文子串的長度
        maxj = 0                                        # 記錄對i右邊影響最大的字元位置j
        maxl = 0                                        # 記錄j影響範圍的右邊界
        maxd = 0                                        # 記錄最長的迴文子串長度
        for i in range(lens):                           # 遍歷字串
            if maxl > i:
                count = min(maxl-i, int(f[2*maxj-i]/2)+1)  # 這裡為了方便後續計算使用count,其表示當前字元到其影響範圍的右邊界的距離
            else :
                count = 1
            while i-count >= 0 and i+count < lens and s[i-count] == s[i+count]:  # 兩邊擴充套件
                count += 1
            if(i-1+count) > maxl:                         # 更新影響範圍最大的字元j及其右邊界
                maxl, maxj = i-1+count, i
            f.append(count*2-1)
            maxd = max(maxd, f[i])                       # 更新迴文子串最長長度
        return int((maxd+1)/2)-1                        # 去除特殊字元

4. 動態規劃

基本思路是對任意字串,如果頭和尾相同,那麼它的最長迴文子串一定是去頭去尾之後的部分的最長迴文子串加上頭和尾。如果頭和尾不同,那麼它的最長迴文子串是去頭的部分的最長迴文子串和去尾的部分的最長迴文子串的較長的那一個。
P[i,j]P[i,j]

   def longestPalindrome(s):
        n = len(s)
        maxl = 0
        start = 0
        for i in xrange(n):
            if i - maxl >= 1 and s[i-maxl-1: i+1] == s[i-maxl-1: i+1][::-1]:
                start = i - maxl - 1
                maxl += 2
                continue
            if i - maxl >= 0 and s[i-maxl: i+1] == s[i-maxl: i+1][::-1]:
                start = i - maxl
                maxl += 1
        return s[start: start + maxl]

5. Discuss看到的一個解法

while k < lenS - 1 and s[k] == s[k + 1]: k += 1 is very efficient and can handle both odd-length (abbba) and even-length (abbbba).

def longestPalindrome(self, s):
    lenS = len(s)
    if lenS <= 1: return s
    minStart, maxLen, i = 0, 1, 0
    while i < lenS:
        if lenS - i <= maxLen / 2: break
        j, k = i, i
        while k < lenS - 1 and s[k] == s[k + 1]: k += 1
        i = k + 1
        while k < lenS - 1 and j and s[k + 1] == s[j - 1]:  k, j = k + 1, j - 1
        if k - j + 1 > maxLen: minStart, maxLen = j, k - j + 1
    return s[minStart: minStart + maxLen]

參考文獻

  1. https://www.felix021.com/blog/read.php?2040
  2. http://blog.csdn.net/backkom_lory/article/details/53896664
  3. https://leetcode.com/problems/longest-palindromic-substring/discuss/3190/