[Leetcode解題] 1143. Longest Common Subsequence - 使用 Rolling DP 指針解
26 July 2024
medium
dp
題目
1143. Longest Common Subsequence
給定兩個字串 text1 和 text2,返回它們的最長共同子序列的長度。如果沒有共同的子序列,返回 0。
解題思路 - DP
這題的目的是找出兩個字串的最長共同子序列。我們可以使用動態規劃來解決這個問題。
具體步驟如下:
- 建立一個二維的 DP 陣列,其中
dp[i][j]表示text1的前i個字符和text2的前j個字符的最長共同子序列的長度。 - 初始化邊界條件,即當
i或j為 0 時,dp[i][j]應該為 0,因為一個字串和空字串的最長共同子序列長度為 0。 - 遍歷
text1和text2,如果text1[i-1]等於text2[j-1],則dp[i][j] = dp[i-1][j-1] + 1,否則dp[i][j] = max(dp[i-1][j], dp[i][j-1])。 - 最後,
dp[m][n]即為最長共同子序列的長度,其中m和n分別是text1和text2的長度。
範例
假設有兩個字串:
text1 = "abcde"text2 = "ace"
1. 建立一個 2D DP 陣列,初始化為 0
| a | c | e | ||
|---|---|---|---|---|
| 0 | 0 | 0 | 0 | |
| a | 0 | 0 | 0 | 0 |
| b | 0 | 0 | 0 | 0 |
| c | 0 | 0 | 0 | 0 |
| d | 0 | 0 | 0 | 0 |
| e | 0 | 0 | 0 | 0 |
2. 逐步填充 DP 陣列
| a | c | e | ||
|---|---|---|---|---|
| 0 | 0 | 0 | 0 | |
| a | 0 | 1 | 1 | 1 |
| b | 0 | 1 | 1 | 1 |
| c | 0 | 1 | 2 | 2 |
| d | 0 | 1 | 2 | 2 |
| e | 0 | 1 | 2 | 3 |
- 當
text1[i-1]等於text2[j-1],代表當前字符是共同字符,dp[i-1][j-1]是不包括這個共同字符之前的 LCS 長度,因此dp[i][j]是dp[i-1][j-1]加 1*- e.g.
"ac"與"abcd"的LCS是2,如果兩個字串都加上'e',"ace"與"abcde"的LCS 是"ac"與"abcd"的LCS加1
- e.g.
- 否則,
dp[i][j] = max(dp[i-1][j], dp[i][j-1])- 這表示當
text1[i-1]不等於text2[j-1]時,LCS 的長度不會增加。 - 我們需要在兩個選擇中取最大的 LCS 長度:
dp[i-1][j]或dp[i][j-1]。dp[i-1][j]是忽略text1[i-1],只考慮text1的前 $i-1$ 個字符和text2的前 $j$ 個字符的 LCS。dp[i][j-1]是忽略text2[j-1],只考慮text1的前 $i$ 個字符和text2的前 $j-1$ 個字符的 LCS。
- 這表示當
3. 輸出結果
最後 DP 陣列中的右下角 dp[5][3] 就是 “abcde” 和 “ace” 的最長共同子序列的長度,即 3。
Python 代碼實現
def longestCommonSubsequence(text1, text2):
m, n = len(text1), len(text2)
dp = [[0] * (n + 1) for _ in range(m + 1)]
for i in range(1, m + 1):
for j in range(1, n + 1):
if text1[i - 1] == text2[j - 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[m][n]
複雜度分析
- 時間複雜度:O(m * n),其中 m 和 n 分別是
text1和text2的長度。我們需要遍歷 DP 陣列中的每一個元素。 - 空間複雜度:O(m * n),我們使用了一個二維陣列來存儲中間結果。
解題思路 - rolling DP
避免重複計算。使用 rolling DP(滾動動態規劃)的方法可以節省空間。
具體步驟
-
建立 DP 陣列:我們只需要兩個一維陣列
previous和current來儲存每一步的計算結果。previous保存上一行的 DP 值,current保存當前行的 DP 值。 -
初始化:初始化
previous和current陣列為 0,長度為text1的長度加 1。 -
遍歷字符:遍歷
text2的每個字符,對每個字符再遍歷text1的每個字符進行比較。如果字符相同,則current[i] = previous[i-1] + 1;否則,current[i] = max(previous[i], current[i-1])。 -
更新陣列:每次內層循環結束後,將
current複製到previous,以便進行下一輪比較。 -
結果:最終,
previous陣列的最後一個元素就是最長共同子序列的長度。
Python 代碼實現
def longestCommonSubsequence(text1, text2):
m, n = len(text1), len(text2)
previous = [0] * (m + 1)
current = [0] * (m + 1)
for j in range(1, n + 1):
for i in range(1, m + 1):
if text1[i - 1] == text2[j - 1]:
current[i] = previous[i - 1] + 1
else:
current[i] = max(previous[i], current[i - 1])
previous = current[:]
return previous[m]
複雜度分析
- 時間複雜度:O(m * n),其中 m 和 n 分別是
text1和text2的長度。我們需要遍歷 DP 陣列中的每一個元素。 - 空間複雜度:O(m),使用了兩個長度為 m + 1 的一維陣列來存儲中間結果。