1. 程式人生 > >LeetCode Medium 5 最長迴文子串 Python

LeetCode Medium 5 最長迴文子串 Python

def longestPalindrome(self, s):
    """
    演算法:動態規劃DP
    思路:
        本題中的動規其實是對暴力解法的一種優化,所以還是,先思考暴力解法怎麼做?
        暴力解法:
            遍歷所有的可能的字串,for ... for ... 兩層 ,然後依次判斷每個字串是否是迴文串,s==s[::-1],逆序ON
        所以暴力解法的時間複雜度是ON3
        動規:
            動規就是在暴力解法的基礎上,優化了判斷子串是否為迴文串這一步。判斷某個位置i,j是不是迴文串,可以先看看(i+1,j-1)
        是不是迴文串並且是否s[i]==s[j],故用dp儲存s(i,j)是否為迴文串,如此便可以建立起狀態轉移方程:
            dp[i][j]= dp[i][j] and s[i]==s[j]
            邊界條件:
                dp[i][i] = True
                dp[i][i+1] = s[i]==s[i+1]
            如此一來便可以在暴力解法的基礎上進行優化改進,將時間複雜度進一步壓縮
        ❗️❗️❗️:
            要注意實現的時候,兩層for的順序!
            譬如以下程式碼中的後半部分,我原來寫的是
            for i in range(len(s)):
                for j in range(i+1,len(s)):
                    #CODE
            這樣寫是一個很符合常規思路的想法,就是遍歷所有的子串嘛,但是!一定要記得!動規的題,dp的狀態是什麼是很有講究的!
        因為狀態轉移方程要和子狀態建立起關係,所以要考慮我們這裡的dp存的是什麼。以及這個狀態轉移的【路徑】是否正確!就像
        二維矩陣機器人往右下角最少走幾步的那種題一樣,機器人是可以向右向下走的,所以看一下這裡的【路徑】是什麼
            拿一個上面這種for跑錯的結果為示例
            case:"abcba",answer:"bcb"e
            為什麼錯了?為什麼不是"abcba"?
            可以看到,當i==0,j== 4時 dp[0][4] = dp[1][3] and s[0] == s[4],而遍歷到0,4時,1,3還沒有遍歷!也就是說我依賴的子
        狀態還沒有求解!所以會導致錯誤的答案!
            那麼聯想其他一些題的dp做法,比如一個一維陣列的最大子序列和,dp存的是子序列最後一位是當前i的最大子序列和!,所以這裡也是
        一樣的,dp是存i,j是否為迴文串沒錯,但是遍歷順序是以j為結尾的子串是否為迴文串!所以改寫for迴圈為
            for j in range(len(s)):
                for i in range(j - 1):
                    #CODE
            這樣case="abcba"時,j=4,i=0,代表的是以s[4]結尾的子串是否為迴文串,這個時候再去找s[1][3]的時候,已經建立了s[1][3]的值!
        ✨✨✨:
            這種感覺就像是,反正都是兩層for 都能把所有的子串都遍歷,生成一遍,但是兩層for的遍歷方式也有很多,要選擇一種和動態規劃
        狀態轉移方式,能正確地將子狀態和當前狀態保證先後順序連線起來的那種遍歷方式!
    複雜度分析:
        時間:ON2,顯然
        空間:ON2,dp陣列空間
    """
    if s == '':
        return s
    ans = s[0]
    dp = [[i == j for j in range(len(s))] for i in range(len(s))]
    for i in range(len(s) - 1):
        dp[i][i + 1] = s[i] == s[i + 1]
        if dp[i][i + 1] and len(ans) < 2:
            ans = s[i:i + 2]
    for j in range(len(s)):
        for i in range(j - 1):
            dp[i][j] = dp[i + 1][j - 1] and s[i] == s[j]
            ans = s[i:j + 1] if dp[i][j] and len(s[i:j + 1]) > len(ans) else ans
    return ans

def longestPalindrome1(self, s):
    """
    演算法:從中間擴散
    思路:
        換個角度看回文串,將回文串看做是從中間向兩邊擴充套件的,
        比如,
            'abcba'就可以看做是從c擴充套件,左右都是b,左右都是a,
            'abba',可以看做中間是bb,左右都是a
        所以對每一個位置i取嘗試左右最大能擴充套件到多少,
            要注意要擴充套件兩次:
                一次找奇數長度的迴文序列:中間位置i,i
                一次找偶數長度的迴文序列:中間位置i,i+1
            helper中return的是s(l:r)而不是s[l:r],因為向外拓展的時候左右會擴,擴到跳出while迴圈後,說明是不滿足while
        的條件的(即當前是迴文,可以擴充套件的條件),說明上一個狀態是滿足【合法且是迴文的】,所以要返回上一個狀態即s(l:r)-->s[l+1,r]
    複雜度分析:
        時間:ON2,顯然
        空間:O1,常數級
    """
    res = ""

    def helper(s, l, r):
        while l >= 0 and r < len(s) and s[l] == s[r]:
            l -= 1
            r += 1
        return s[l + 1:r]

    for i in range(len(s)):
        #擴充套件奇數長度
        tmp = helper(s, i, i)
        if len(tmp) > len(res):
            res = tmp
        #擴充套件偶數長度
        tmp = helper(s, i, i + 1)
        if len(tmp) > len(res):
            res = tmp
    return res