[LeetCode 筆記] 5. Longest Palindromic Substring

Emmie Lin
5 min readMay 14, 2022

--

題目連結

題意簡述

給定一組字串,回傳該組字串中,最長的回文(Palindromic)子字串。例如: 輸入babad ,回傳 bab (或 aba)。

解題思路

解法一:暴力法

我第一次也只能想到暴力法,作法很簡單,就是把每個子字串拿去判斷是否為回文。同樣以 babad 為例,從 index = 0 開始逐一檢查 b , ba , bab , baba , babad 是否為回文,再來從 index = 1 開始檢查 a, ab , aba , abad ,將每個子字串都跑過一次。

實作程式碼如下:

而這個解法在 input 字串長度不長的 test case 下是都能成功跑過,但當 input 字串非常長時,就直接 GG 惹。

而我們來看一下他的時間複雜度,外面兩個迴圈在組合所有子字串的過程, 總共跑了 n + (n-1) + (n-2) + … + 1 = n(n-1) / 2 次,就已經達到了 O(n²) 。而每次對該字串檢查是否為回文,同樣需要遍歷每個子字串於是再乘以 n,複雜度來到了 O(n³)。如此高的時間複雜度,會直接 time out 也合理了。

解法二:檢查回文方法改成由內而外

這個版本的解法核心在於:改變我們檢查是否為回文的方法。由於暴力法中檢查回文的方式是傳統的由外而內方式檢查,也就是說,以 bab 為例, 先從頭尾開始檢查是否相同,若是,再將兩邊指標各往中間移動一格,繼續檢查下去。

然而這裡會需要改成由內而外的方式檢查,也就是先認定某個字元為中心點,再往外去檢查左右兩側是否為相同字元。所以可以只需要一層外迴圈遍歷每個字元,在每次的遍歷中,都將當前字元視為回文字串的中心,去往外擴展看能夠達到最長的回文子字串長度。

但這個方法需要另外考慮字串為偶數的情況,例如 cbbd ,其中最長的回文子字串為 bb ,這個情況下要將 bb 視為中心。

實作程式碼如下:

這個解法可以將複雜度下降到 O(n²):遍歷每個字元 ,每次找尋最大回文子字串再遍歷2次,O(n*2n) 。

解法三:Dynamic Programming

最後來到最難的 DP 解法⋯⋯首先將每個子字串以 (起始位置, 結束位置)的座標來代表,例如:以 babad 為例,(0,2) 代表 起始位置為 index = 0(“b”), 結束位置為 index = 2 (”b”)的子字串 bab 。並且建立一個 row 為起始位置,col 為結束位置的二維陣列,來紀錄該字串是否為回文。

將所有子字串列出

首先來看到 (0,0) 的位置,代表的是起始位置為 0 且結束位置也為 0 的子字串 “b”,而根據回文規則,單一字元視為回文字串,所以在表格上(0,0)裡面可以填 true (代表其為回文字串)。

同理,(1,1)、(2,2)⋯⋯所有長度為 1 的子字串都是回文,於是表格填完會長這樣:

填完所有長度為 1 的子字串後的表

這時候來到 DP 的精髓,也就是如何有效運用前一次的計算結果呢?

假設我們有一組子字串 cabac 座標為(i,j),如果起始位置(i)與結束位置(j)的字元相同,並且中間字串 aba 也是回文的情況下,我們就可以保證,這組子字串也會是回文。

由此可以推導出字串要是回文必須滿足兩個條件:

  1. 起始位置的字元需等於結束位置的字元
  2. 中間的那組子字串也必須是回文

以座標為(0,2) 的子字串 bab為例:起始位置的字元 b等於結束位置的字元 b。同時查表得知中間的 a 也就是字串(1,1) 是回文,達成條件 1&2 所以該子字串也是回文,並記錄在表上。

而在 (0,3) 的子字串 baba 情況下,起始位置字元 b 與結束位置字元 a 不同,直接不符合回文,也無需再檢查條件 2。

所以我們可以歸納出一個公式:

// pseudo codesubstring(i, j) = string[i] == string[j] && substring(i+1, j-1)

實作程式碼如下:

這個解法的時間複雜度和前者相同為 O(n²):和暴力法同樣窮舉了所有子字串的組合,但節省了每次計算是否為回文的成本,改用記憶法代替。而空間複雜度的部分,由於需要組成一個 2D Array,同樣為 O(n²)。

參考資料

下面影片都講解得很清楚~推!

解法二

解法三

--

--