leetcode 解題 5. 最長迴文子串 python@ 官解,暴力法,動態法,manacher 法

娃哈哈店長發表於2019-12-26

給定一個字串 s,找到 s 中最長的迴文子串。你可以假設 s 的最大長度為 1000。

暴力法

很明顯,暴力法將選出所有子字串可能的開始和結束位置,並檢驗它是不是迴文。
時間複雜度:O(n^2),往往利用python的切片可以很好的縮減複雜度
如果不用切片,還需要遍歷一次子字串,時間複雜度就是O(^3)
空間複雜度:O(1)

ps: 官方給出的時間複雜度是沒有考慮python切片的功能

def force(self, s: str) -> str:

        if s==s[::-1]:
            return s
        max_len = 1
        res = s[0]
        for i in range(len(s) - 1):
            for j in range(i + 1, len(s)):
                if j - i + 1 > max_len and s[i:j+1] == s[i:j+1][::-1]:
                    max_len = j - i + 1
                    res = s[i:j + 1]
        return res

動態規劃,中心擴散

中心擴散法:
為了改進暴力法,我們首先觀察如何避免在驗證迴文時進行不必要的重複計算。考慮“ababa” 一定是迴文,因為它的左首字母和右尾字母是相同的。
我們給出 P(i,j) 的定義如下:
如果子串S_i和S_j是迴文字串則P(i,j)為ture
其他情況,P(i,j)為false
因此 P(i,j)=(P(i+1,j−1) and S_i==S_j)
基本示例如下:
P(i, i) = true
P(i, i+1) = ( Si == S{i+1} )
這產生了一個直觀的動態規劃解法,我們首先初始化一字母和二字母的迴文,然後找到所有三字母迴文,並依此類推…
複雜度分析
時間複雜度:O(n^2)
空間複雜度:O(1)

 #中心擴散法Spread From Center
    def spread(self, s, left, right):
        """
        left = right 的時候,此時迴文中心是一條線,迴文串的長度是奇數
        right = left + 1 的時候,此時迴文中心是任意一個字元,迴文串的長度是偶數
        """

        while left >= 0 and right < len(s) and s[left] == s[right]:
            left -= 1
            right += 1
        return s[left + 1:right]

# 動態規劃法-中心擴散法Spread From Center
    def spread_from_center(self, s:str) -> str:

        if s==s[::-1]:
            return s
        res = s[:1]
        for i in range(len(s)):
            palindrome_odd= self.spread(s,i, i)
            palindrome_even= self.spread(s,i, i + 1)
            # 當前找到的最長迴文子串
            res = max(palindrome_odd,palindrome_even,res,key=len)
        return res

manacher法

這是一個複雜度為 O(n) 的 Manacher 演算法。
假如字串是奇數個,那麼我們可以透過遍歷所有字串,再對所有字串進行左右匹配,就像中心擴散方法一樣。然後得到長度最大的字串
但是如果字串是偶數個,我們無法進行此操作
這個演算法的最終要的額一點就是,我們將一個偶數長/奇數長的字串,構造成新的字串。
這樣我們可以對新字串的每個字元,進行左右匹配。


# manacher法專門用來解決迴文字串的問題
def mancher(self, s:str) -> str:
if len(s) < 2:
return s
# 將一個可能是偶數長/奇數長的字串,首位以及每個字元間新增#
test = '#'+'#'.join(s)+'#'
# 當前遍歷的中心最大擴散步數,其值等於原始字串的最長迴文子串的長度
max_len = 0
for i in range(len(test)):
left = i - 1
right = i + 1
step = 0
print(test[i])
while left >= 0 and right < len(test) and test[left] == test[right]:
# print("spread",test[left],test[right])
left -= 1
right += 1
step += 1
# print(step)
        if step > max_len:
            max_len = step
            start = (i - max_len) // 2
    return s[start: start + max_len]

[原始碼儲存在github上,歡迎來提bug哦!-點選訪問](https://github.com/Freen247/leetcode)
如果覺得不錯請給我一個star謝謝了Stray_Camel(^U^)ノ~YO
本作品採用《CC 協議》,轉載必須註明作者和本文連結
文章!!首發於我的部落格Stray_Camel(^U^)ノ~YO

相關文章