【LeetCode教學】1143. Longest Common Subsequence

給定兩個字串,傳回它們最長公共子序列(longest common subsequence, LCS)的長度。

題目連結:連結點此

題目描述

給定兩個字串 text1 和 text2,傳回它們最長公共子序列(longest common subsequence, LCS)的長度。如果沒有公共子序列,則傳回 0。

字串的子序列(subsequence)是指在不改變剩餘字元相對順序的情況下,從原始字串中刪除部分字元(可以不刪除)產生的新字串。

例如,“ace”是“abcde”的子序列。

兩個字串的公共子序列(common subsequence)是指兩個字串共同的子序列。

題目限制

  • 1 <= text1.length, text2.length <= 1000
  • text1 and text2 consist of only lowercase English characters.

解題方法

1. 首先我們可以把text1的每個字元當成橫列,把text2的每個字元當成縱列,並且宣告一個二維向量儲存兩個輸入字串的每個字元的LCS長度,dp[i][j]表示text1前i個字符(text1[0:i])與text2前j個字符(text2[0:j])的LCS長度。

int m = text1.length();
int n = text2.length();
vector<vector<int>> dp(m, vector<int>(n, 0));

我們使用text1=”abcde”、text2=”ace”作為範例,宣告的二維向量dp會是下列表格的樣子。第一個直行和第一個橫列為空字元,但dp宣告時不包含空字元的行列,計算時會直接把他當為數值0。

abcde
000000
a0dp[0][0]dp[1][0]
c0dp[0][1]
e0

2. 用雙層for迴圈遍歷text1和text2的每個字元。


for (int i = 0; i < m; i++)  //遍歷text1的每個字元
{
    for (int j = 0; j < n; j++)  //遍歷text2的每個字元
    {
    }
}

3. 若該格子對應的兩個字母相同,答案 = 1 + 左斜上格。                   

dp[i-1][j-1] 表示 text1 前 i-1 個字符(text1[0:i-1])與 text2 前 j-1 個字符(text2[0:j-1])的 LCS 長度,也就是不包含當前字符 text1[i-1] 和 text2[j-1] 的子問題答案,如果 text1[i] == text2[j],則當前字符可以加入 LCS,總長度增加 1,因此 dp[i][j] = dp[i-1][j-1] + 1。

if (text1[i] == text2[j])
{
    if (i > 0 && j > 0) dp[i][j] = 1 + dp[i - 1][j - 1];
    else dp[i][j] = 1 + 0;
}

4. 若該格子對應的兩個字母不相同,答案 = Max(左邊格, 上面格)。

如果text1[i] != text2[j],表示text1的當前字元和text2的當前字元都無法加入LCS,此時我們需要從以下兩個子問題作選擇:

  • 忽略 text1的當前字符:計算 text1 的前 i-1 個字符與 text2 的前 j 個字符的 LCS,即 dp[i-1][j](上面格)。
  • 忽略 text2 的當前字符:計算 text1 的前 i 個字符與 text2 的前 j-1 個字符的 LCS,即 dp[i][j-1](左邊格)。

因此我們選擇兩種子問題中 LCS 長度較大的那一個。

else
{
    if (i > 0 && j > 0) dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
    else if (i > 0) dp[i][j] = dp[i - 1][j];
    else if (j > 0) dp[i][j] = dp[i][j - 1];
    else dp[i][j] = 0;
}

遍歷完成後,二維向量dp會是下列表格的樣子。

abcde
000000
a011111
c011222
e011223

5. 返回最後一個格子的數值:dp[m – 1][n – 1],意思為text1的字元[0:m-1]與text2的字元[0:n-1]的LCS長度,即題目要求。

return dp[m - 1][n - 1];

完整程式碼

class Solution {
public:
    int longestCommonSubsequence(string text1, string text2) {

        //1.變數宣告
        //把text1的每個字元當成橫列,把text2的每個字元當成縱列
        int m = text1.length();
        int n = text2.length();
        vector<vector<int>> dp(m, vector<int>(n, 0)); 

        //2.用雙層for迴圈遍歷text1和text2的每個字元
        for (int i = 0; i < m; i++)  //遍歷text1的每個字元
        {
            for (int j = 0; j < n; j++)  //遍歷text2的每個字元
            {
                //3.若該格子對應的兩個字母相同,答案 = 1 + 左斜上格
                if (text1[i] == text2[j])  
                {
                    if (i > 0 && j > 0) dp[i][j] = 1 + dp[i - 1][j - 1];
                    else dp[i][j] = 1 + 0;
                }
                //4.若該格子對應的兩個字母不相同,答案 = Max(左邊格, 上面格)
                else  
                {
                    if (i > 0 && j > 0) dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
                    else if (i > 0) dp[i][j] = dp[i - 1][j];
                    else if (j > 0) dp[i][j] = dp[i][j - 1];
                    else dp[i][j] = 0;
                }
            }
        }        

        //5.返回最後一個格子的數值
        return dp[m - 1][n - 1];
    }
};

測試資料

此題目最經典的測資,驗證當一個字串是另一個字串的子序列時,DP 表格的填寫是否正確。

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

測試當兩個字串完全相同時,程式是否正確返回字串的完整長度。

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

測試當兩個字串完全沒有共同字符時,程式是否正確返回 0。

Input: text1 = "abc", text2 = "def"
Output: 0
Explanation: There is no such common subsequence, so the result is 0.

測試最小輸入(edge case)的處理。

Input: text1 = "a", text2 = "a"
Output: 1

時間與空間複雜度

時間複雜度:O(m*n),其中 m 和 n 分別為 text1 和 text2 的長度。因為需要遍歷 m * n 的 dp 表格。

空間複雜度:O(m*n),因為使用了 m * n 的二維陣列儲存子問題答案。

其他相關題目

  • 62. Unique Paths

返回頂端