1. 程式人生 > 實用技巧 >1143. Longest Common Subsequence

1143. Longest Common Subsequence

問題:

給定兩個字串,求他們的最長公共子序列的長度。

Example 1:
Input: text1 = "abcde", text2 = "ace" 
Output: 3  
Explanation: The longest common subsequence is "ace" and its length is 3.

Example 2:
Input: text1 = "abc", text2 = "abc"
Output: 3
Explanation: The longest common subsequence is "abc" and its length is 3.

Example 3:
Input: text1 = "abc", text2 = "def"
Output: 0
Explanation: There is no such common subsequence, so the result is 0.
 
Constraints:
1 <= text1.length <= 1000
1 <= text2.length <= 1000
The input strings consist of lowercase English characters only.

  

解法:DP(動態規劃)

1.確定【狀態】:

  • 字串text1的第i個字元:text1[i]
  • 字串text2的第j個字元:text2[j]

2.確定【選擇】:分兩種情況

  • text1[i] == text2[j]:
    • 前一個字元狀態(公共序列長度)+1: dp[i-1][j-1] + 1
  • text1[i] != text2[j]:有以下3種情況,取最大值。
    • 只有text1[i]是最終公共子序列的一個字元 -> =上一個包含text1[i]而不包含text2[j]的字元狀態:dp[i][j-1]
    • 只有text2[j]是最終公共子序列的一個字元->=上一個不包含text1[i]而包含text2[j]的字元狀態:dp[i-1][j]
    • 兩個字元都不是最終公共子序列的一個字元 ->=上一個既不包含text1[i]又不包含text2[j]的字元狀態:dp[i-1][j-1]
      • ★由於dp[i-1][j-1]一定<=dp[i][j-1] or dp[i-1][j],因此可以省略比較dp[i-1][j-1]

3. dp[i][j]的含義:

對比到text1的第 i 個字元,text2的第 j 個字元為止,兩個字串的最大公共子序列長度。

4. 狀態轉移:

dp[i][j]=

  • (text1[i] == text2[j]):=前一個字元狀態+1:dp[i-1][j-1] + 1
  • (text1[i] != text2[j]):=max {
    • 上一個包含text1[i]字元的狀態:dp[i][j-1]
    • 上一個包含text2[j]字元的狀態:dp[i-1][j]
    • 上一個text1[i]text2[j]都不包含的狀態:dp[i-1][j-1](★可省略) }

5. base case:

  • dp[0][j]=0:text1為空串,則其與text2的公共子序列也一定為空串,長度為0。
  • dp[i][0]=0:text2為空串,則其與text1的公共子序列也一定為空串,長度為0。

程式碼參考:

 1 class Solution {
 2 public:
 3     //dp[i][j]:until text1[i],text2[j]. the max length of the LCS
 4     //case_1: text1[i]==text2[j]:
 5     //        this character must be in LCS. we need add 1 to pre status:dp[i-1][j-1].
 6     //        =dp[i-1][j-1]+1
 7     //case_2: text1[i]!=text2[j]
 8     //        text1[i] or text2[j] may in LCS. we choose the max of these possibilities.
 9     //        if text1[i] is in LCS, 
10     //        = dp[i][j-1]
11     //        if text2[j] is in LCS, 
12     //        = dp[i-1][j]
13     //        if none of them is in LCS, 
14     //        = dp[i-1][j-1]
15     // note: dp[i-1][j-1]<=dp[i-1][j](dp[i][j-1]) ,so we can ignore it
16     //        =max(dp[i][j-1],dp[i-1][j])
17     //base case:
18     //dp[i][0]=0
19     //dp[0][j]=0
20     int longestCommonSubsequence(string text1, string text2) {
21         int m = text1.length(), n = text2.length();
22         vector<vector<int>> dp(m+1, vector<int>(n+1, 0));
23         for(int i=1; i<=m; i++) {
24             for(int j=1; j<=n; j++) {
25                 if(text1[i-1]==text2[j-1]) {
26                     dp[i][j]=dp[i-1][j-1]+1;
27                 } else {
28                     dp[i][j]=max(dp[i-1][j], dp[i][j-1]);
29                 }
30             }
31         }
32         return dp[m][n];
33     }
34 };

♻️ 優化:

空間複雜度:2維->1維

去掉 i

壓縮所有行到一行。

左上角dp[i-1][j-1]會被下面的dp[i][j-1]覆蓋,因此引入變數pre,在更新dp[i][j-1]之前,儲存dp[i-1][j-1]

程式碼參考:

 1 class Solution {
 2 public:
 3     int longestCommonSubsequence(string text1, string text2) {
 4         int m = text1.length(), n = text2.length();
 5         vector<int> dp(n+1, 0);
 6         for(int i=1; i<=m; i++) {
 7             int pre = 0;
 8             for(int j=1; j<=n; j++) {
 9                 int tmp = dp[j];
10                 if(text1[i-1]==text2[j-1]) {
11                     dp[j]=pre+1;
12                 } else {
13                     dp[j]=max(dp[j], dp[j-1]);
14                 }
15                 pre = tmp;
16             }
17         }
18         return dp[n];
19     }
20 };