5. 最長迴文子串

最長迴文子串

題目描述

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

示例 1:
  • 輸入: “babad”
  • 輸出: “bab”
  • 注意: “aba” 也是一個有效答案。
示例 2:
  • 輸入: “cbbd”
  • 輸出: “bb”

題解

所謂迴文子串,首先是一個字符串s 的 子串(如,sl...srs_l,...,s_r)而且是連續的;其次是迴文,就是正着讀和倒着讀該字符串都是一樣的。

方法一: 暴力

  • 時間複雜度: O(n3n^3)
  • 空間複雜度: O(nn)
思路

枚舉該字符串的每一個子串,並用O(n)的時間複雜度來判斷該子串是否是一個迴文串。思路很簡單,但是很耗時間。

python代碼
class Solution:
    def longestPalindrome(self, s: str) -> str:
        n = len(s)
        ans = [1, 0, 1]  # 最長迴文子串的長度, 最長迴文子串的左邊界, 右邊界
        for i in range(n):
            for j in range(i, n):
                if j-i+1<=ans[0]: continue
                i0, j0 = i, j
                yes = True
                while i0<=j0:
                    if s[i0] != s[j0]:
                        yes = False
                        break
                    i0 += 1
                    j0 -= 1
                if yes and j-i+1 > ans[0]:
                    ans = [j-i+1, i, j+1]
        return s[ans[1]: ans[2]]

方法二:動態規劃(區間DP)

  • 時間複雜度:O(n2n^2)
  • 空間複雜度:O(n2n^2)
思路

想在整個字符串得出最長迴文子串,我們無法一下得出結果。但是,我們可以很容易的求出小字符串是否是迴文串。比如:“aa”、“asa”。像這樣的我們可以一眼就能看出他們是不是迴文串。在此基礎上,我們可以很輕鬆判斷他們是不是迴文串,如:“caac”、“caa”、“wasad”。然後再用一個二維數組來存儲每一個子串是否是迴文串。現在我們就只需要判斷兩邊的字符是否相等以及裏面的字符串是否是迴文串來判斷長的字符串是否是迴文串。由小串逐漸推向大串,最終得到整個字符串的最長迴文子串。結果存儲在dp數組中。經過一次遍歷求出最長迴文串。
dp[l][r] : 表示子字符串sl,sl+1,...,sr1,srs_l, s_{l+1}, ...,s_{r-1} , s_r 是否是迴文串。0表是不是;1表示是。遞推公式如下:

dpl,r={1if:s[l]==s[r]len<30if:s[l]!=s[r]len<3dpl1,r+1if:s[l]==s[r]0if:s[l]!=s[r] \begin{aligned} dp_{l, r}=\left\{ \begin{array}{clc} 1 & & if:s[l] == s[r]、len < 3\\ 0 & & if:s[l] != s[r] 、len < 3\\ dp_{l-1, r+1} & & if:s[l] == s[r]\\ 0 & & if:s[l] != s[r] \end{array} \right. \end{aligned}

  • 當子字符串的長度小於3並且兩端字符串相等時,就表明子串是迴文串!
  • 當子字符串的長度大於等於3時,如果兩端字符串相等,那該子串是否迴文就取決於去掉兩端後的子串;如果不等,直接賦值爲零。
Python代碼
class Solution:
    def longestPalindrome(self, s: str) -> str:
        n = len(s)
        if n == 0: return ''  # 當輸入爲空串的時候,輸出也爲空串
        dp = [[0 for _ in range(n)] for _ in range(n)]  # 不要使用dp[[0]*n]*n
        for i in range(n):  # 對dp數組進行預處理
            dp[i][i] = 1

        for i in range(2, n + 1):  # 子字符串的長度
            for l in range(n):  # 遍歷左端
                if l + i > n: break
                r = l + i - 1  # 右端
                if s[l] == s[r]:
                    if i == 2 or i == 3:
                        dp[l][r] = 1
                        continue
                    dp[l][r] = dp[l + 1][r - 1]
                else:
                    dp[l][r] = 0
        
        for i in range(n, 0, -1):  # 子字符串的長度
            for l in range(n):
                if l + i > n: break
                r = l + i - 1
                if dp[l][r] == 1:
                    return s[l: r+1]  # 返回結果

因爲我對python還不是很瞭解,所以寫程序的時候我碰到了一個很大的坑。就是我在初始數組dp的時候是這樣寫的dp[[0]*n]*n。但是當我預處理時。數組中所有的值都變成1了。有知道爲啥的可以教教我。

方法三:

  • 時間複雜度:O(n2n^2)
  • 空間複雜度:O(11)
思路

在方法一中,我們枚舉每個字符串的時候是以左右兩邊界枚舉的。這樣枚舉雖然可以把每一個子字符串都枚舉出來。但是那些不是迴文串的也被無用的枚舉。但是我們假設我們枚舉的都是迴文串。以他們的中心軸爲標準進行枚舉。就可以時間複雜度從O(n3n^3)降到O(n2n^2)。

python代碼
class Solution:
    def longestPalindrome(self, s: str) -> str:
        n = len(s)
        if n == 0: return ''
        ans = [1, -1, 1]
        for i in range(n):  # 以字符s[i]/s[i-1]S[I]爲中心軸
            l = i-1
            r = i+1
            while r<n and s[r] == s[i]:
                r += 1
            while l>=0 and r<n and s[l] == s[r]:
                l -= 1
                r += 1
            if ans[0] < (r-l-1):
                ans = [r-l-1, l, r]

        return s[ans[1]+1: ans[2]]

方法四:Manacher算法(馬拉車算法)

  • 時間複雜度:O(nn)
  • 空間複雜度:O(nn)
思路
python代碼
class Solution:
    def longestPalindrome(self, s: str) -> str:
        if len(s) < 2: return s

        def get_str(s):  # 預處理字符串
            str = '$#'  # 以防數組越界
            str += '#'.join(list(s))
            str += '#'
            return str
        def mancher(s):
            n = len(s)
            R, C = 0, 0  # 最右迴文右邊界R, 最右迴文右邊界的對稱中心C
            l, r, maxlen = 0, 0, 0  # 最大回文串的左右邊界和最大長度
            radius = [0] * n  # 迴文半徑數組radius
            for i in range(n):
                if R > i:
                    radius[i] = min(R - i, radius[2 * C - i])
                else:
                    radius[i] = 1
                while i + radius[i] < n and i-radius[i] >= 0 and s[i + radius[i]] == s[i - radius[i]]: radius[i] += 1
                if radius[i] + i > R:
                    R = radius[i] + i
                    C = i
                    if maxlen < radius[i]:
                        maxlen = radius[i]
                        l = i-radius[i]+1 # 因爲此時的radius[i]比實際符合條件的要大1, 所以左邊界要加1,右邊界不用加因爲返回時右邊界是開區間。
                        r = i+radius[i]
            return s[l: r]
        
        return mancher(get_str(s))[1::2]  # 去掉中間的 ‘#’
完整python代碼
class Solution:
    def longestPalindrome(self, s: str) -> str:
        if len(s) < 2: return s

        def get_str(s):  # 預處理字符串
            str = '$#'  # 以防數組越界
            str += '#'.join(list(s))
            str += '#'
            return str
        def mancher(s):
            n = len(s)
            R, C = 0, 0  # 最右迴文右邊界R, 最右迴文右邊界的對稱中心C
            l, r, maxlen = 0, 0, 0  # 最大回文串的左右邊界和最大長度
            radius = [0] * n  # 迴文半徑數組radius
            for i in range(n):
                if R > i:
                    radius[i] = min(R - i, radius[2 * C - i])
                else:
                    radius[i] = 1
                while i + radius[i] < n and i-radius[i] >= 0 and s[i + radius[i]] == s[i - radius[i]]: radius[i] += 1
                if radius[i] + i > R:
                    R = radius[i] + i
                    C = i
                    if maxlen < radius[i]:
                        maxlen = radius[i]
                        l = i-radius[i]+1
                        r = i+radius[i]
            return s[l: r]
        
        return mancher(get_str(s))[1::2]  # 去掉中間的 ‘#’

def stringToString(input):
    return input[1:-1].decode('string_escape')

def main():
    import sys
    import io
    def readlines():
        for line in io.TextIOWrapper(sys.stdin.buffer, encoding='utf-8'):
            yield line.strip('\n')

    lines = readlines()
    while True:
        try:
            line = next(lines)
            s = stringToString(line);
            
            ret = Solution().longestPalindrome(s)

            out = (ret);
            print(out)
        except StopIteration:
            break

if __name__ == '__main__':
    main()
發佈了51 篇原創文章 · 獲贊 98 · 訪問量 6564
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章