1. 程式人生 > 其它 >[演算法] 最長迴文串

[演算法] 最長迴文串

最長子迴文串

思路一

使用動態規劃來處理這樣的題目,令一個二維表格\(dp\)記錄結果,其中\(dp[i][j]\)記錄的字串中區間\([i,j]\)之間的最大回文串的長度,這樣我們可以發現初始值:\(dp[i][i]=1\),且在\(i>j\)的情況下均為0,同時,有如下轉移公式:

\[\begin{align} &s[i]=[j]: dp[i][j]=dp[i+1][j-1]+2\\ &s[j]\neq s[j]:dp[i][j]=max(dp[i+1][j],dp[i][j-1]) \end{align} \]

於是可以寫出如下程式碼:

def method1(s: str) -> int:
    # 第一種思路,動態規劃,記錄dp[i][j]記錄[i,j]之間的最大回文串,i==j時:1,i>j時:0
    # 判斷s[i]==s[j]則,dp[i][j]=dp[i+1][j-1]+2,否則dp[i][j]=max(dp[i+1][j],dp[i][j-1])
    # 由此,需要確定,i倒著遍歷,j正著遍歷
    n = len(s)
    dp = [[0] * n for _ in range(n)]
    for i in range(n):
        dp[i][i] = 1
    i = n - 1
    while i >= 0:
        for j in range(i + 1, n):
            if s[i] == s[j]:
                dp[i][j] = dp[i + 1][j - 1] + 2
            else:
                # 這裡解釋一下索引,i+1一定小於等於j小於n,j-1一定大於等於i大於0
                dp[i][j] = max(dp[i + 1][j], dp[i][j - 1])
        i -= 1
    return dp[0][n - 1]

上述方法時間複雜度為\(o(n^2)\),空間複雜度為\(o(n^2)\)

考慮優化一下空間,仔細觀察轉移公式,可以發現迭代過程中,是從最後一行向上迭代,並且迭代每一行的時候,僅依賴於下一行,於是可以考慮使用單獨的一個數組來替代整個的二位陣列,有如下程式碼:

def method2(self, s: str) -> int:
    # 優化一下空間,僅採用兩個向量來記錄結果動態規劃結果
    n = len(s)
    dp = [0] * n
    for i in range(n - 1, -1, -1):
        tem_dp = [0] * n
        tem_dp[i] = 1
        for j in range(i + 1, n):
            if s[i] == s[j]:
                tem_dp[j] = dp[j - 1] + 2
            else:
                tem_dp[j] = max(dp[j], tem_dp[j - 1])
        dp = tem_dp
    return dp[n - 1]

和方法一唯一的不同點在於,僅記錄了原本二維陣列中最新的一行的資料,於是,在時間複雜度不變的情況下,空間複雜度優化到了\(o(n)\)

思路二

將字串做一個反轉後,求兩個字串的最大子序列,這樣就等價於求最長迴文序列;

字串沒有必要實際的進行反轉,僅在遍歷過程中選取對應的字元即可;

程式碼如下:

def method3(self, s: str) -> int:
    # 另一個思路,將字串反轉,然後求解兩個串的最長子序列
    n = len(s)
    dp = [[0] * (n + 1) for _ in range(n + 1)]
    for i in range(1, n + 1):
        for j in range(1, n + 1):
            if s[- j] == s[i - 1]:
                dp[i][j] = dp[i - 1][j - 1] + 1
            else:
                dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])
    return dp[n][n]

求解最長公共子序列是個經典問題,依舊是採用的動態規劃的思路求解,\(dp[i][j]\)記錄的是兩個字串的字串\([0:i+1]\)\([0:j+1]\)的最長字串長度;

演算法的時間複雜度是\(o(n^2)\),空間複雜度也是\(o(n^2)\),但是由於這裡遍歷的時候要將整個二維陣列全部更新一遍,二思路一則僅僅更新了一半的矩陣,因此實際執行時,思路1是要比這個思路快的;

同樣的,從轉移方程可以看出,更新新一行資料時僅依賴上一行的資料,於是我們可以優化下空間複雜度到\(o(n)\),程式碼如下:

def method4(self, s: str) -> int:
    # 順著求最大字串的思路,優化一下空間
    n = len(s)
    dp = [0] * n
    for i in range(0, n):
        temp = [0] * n
        for j in range(0, n):
            if s[n - j - 1] == s[i]:
                temp[j] = dp[j - 1] + 1 if j > 0 else 1
            else:
                temp[j] = max(dp[j], temp[j - 1] if j > 0 else 0)
        dp = temp
    return dp[n - 1]
學習java的新手,嘗試將學到的內容總結為部落格,內容如有錯誤,歡迎指正,感激不盡。