「程式碼隨想錄演算法訓練營」第三十九天 | 動態規劃 part12

云雀AC了一整天發表於2024-08-16

115. 不同的子序列

題目連結:https://leetcode.cn/problems/distinct-subsequences/
文章講解:https://programmercarl.com/0115.不同的子序列.html
題目難度:困難
影片講解:https://www.bilibili.com/video/BV1fG4y1m75Q/
題目狀態:看題解

思路:

  1. 動態規劃陣列初始化
    • 建立一個二維動態規劃陣列 dp,大小為 ((s.size() + 1), (t.size() + 1))。dp[i][j] 表示 s 的前 i 個字元中包含 t 的前 j 個字元的不同子序列的數量。
  2. 邊界條件設定
    • dp[i][0] = 1:表示任何字串 s 的前 i 個字元都可以形成空字串 t 的一種方式(即選擇不選)。
    • dp[0][j] = 0(對於 (j > 0)):表示空字串 s 無法形成非空字串 t
  3. 動規陣列更新
    • 外層迴圈遍歷 s 的每個字元(從 1s.size())。
    • 內層迴圈遍歷 t 的每個字元(從 1t.size())。
    • 如果 s[i-1]t[j-1] 相等:
      • dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j]:表示可以選擇將這個字元包含在子序列中(加上 dp[i-1][j-1])或者不包含(加上 dp[i-1][j])。
    • 如果不相等:
      • dp[i][j] = dp[i - 1][j]:只能選擇不包含 s[i-1]
  4. 結果
    • 返回 dp[s.size()][t.size()],即 s 中包含 t 的不同子序列的數量。

程式碼:

class Solution {
public:
    int numDistinct(string s, string t) {
        int sLen = s.size(), tLen = t.size();
        vector<vector<uint64_t>> dp(sLen + 1, vector<uint64_t>(tLen + 1));
        for(int i = 0; i < sLen; ++i) dp[i][0] = 1;
        for(int j = 1; j < tLen; ++j) dp[0][j] = 0;
        for(int i = 1; i <= sLen; ++i) {
            for(int j = 1; j <= tLen; ++j) {
                if(s[i - 1] == t[j - 1]) dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];
                else dp[i][j] = dp[i - 1][j];
            }
        }
        return dp[sLen][tLen];
    }
};

583. 兩個字串的刪除操作

題目連結:https://leetcode.cn/problems/delete-operation-for-two-strings/
文章講解:https://programmercarl.com/0583.兩個字串的刪除操作.html
題目難度:中等
影片講解:https://www.bilibili.com/video/BV1we4y157wB/
題目狀態:看題解

思路:

維護一個二維動規陣列dp[i][j]用來表示將word1的前i個字元轉換為word2的前j個字元所需的最小操作次數。

初始化dp[i][j]

  • dp[i][0] = i:將word1的前i個字元轉換為空字串需要i次刪除操作。
  • dp[0][j] = j:將空字串轉換為word2的前j個字元需要j次插入操作。

更新動規陣列:
對於每一對ij,我們考慮以下兩種情況:

  • 字元匹配(word1[i-1] == word2[j-1]):
    • 如果當前字元相同,不需要任何操作: dp[i][j] = dp[i - 1][j - 1];
  • 字元不匹配(word1[i-1] != word2[j-1]):
    • 我們可以進行兩種操作:
      • 刪除一次word1的元素:此時需要dp[i - 1][j] + 1次操作次數。
      • 刪除一次word2的元素:此時需要dp[i][j - 1] + 1此操作次數。
    • 取這兩種操作的最小值:min(dp[i - 1][j] + 1, dp[i][j - 1] + 1)

程式碼:

class Solution {
public:
    int minDistance(string word1, string word2) {
        int len1 = word1.size(), len2 = word2.size();
        vector<vector<int>> dp(len1 + 1, vector<int>(len2 + 1));
        for(int i = 0; i <= len1; ++i) dp[i][0] = i;
        for(int j = 0; j <= len2; ++j) dp[0][j] = j;
        for(int i = 1; i <= len1; ++i) {
            for(int j = 1; j <= len2; ++j) {
                if(word1[i - 1] == word2[j - 1]) dp[i][j] = dp[i - 1][j - 1];
                else dp[i][j] = min(dp[i - 1][j] + 1, dp[i][j - 1] + 1);
            }
        }
        return dp[len1][len2];
    }
};

72. 編輯距離

題目連結:https://leetcode.cn/problems/edit-distance/
文章講解:https://programmercarl.com/0072.編輯距離.html
題目難度:中等
影片講解:https://www.bilibili.com/video/BV1qv4y1q78f/
題目狀態:有思路,細節出了問題

思路:

這道題的本質就是在上道題的基礎上做了改變,上道題遇到不相等的字元時只需要考慮刪除操作,這道題在遇到不相等的字元時需要考慮三種情況:刪除、插入、替換。

  • 刪除:在word1中刪除一個字元,也就轉化為了word1[i - 2]word2[j - 1]相比較了,因此dp[i][j] = dp[i - 1][j] + 1
  • 插入:在word1中新增一個字元,其實這個操作得到的最終結果和在word2中刪除一個字元的結果是一樣的,因此就轉化為word1[i - 1]word2[j - 2]相比較了,因此dp[i][j] = dp[i][j - 1] + 1
  • 替換:替換操作就是在word1[i - 1] != word2[j - 1]的前提下進行一次操作使得word1[i - 1] == word2[j - 1],因此dp[i][j] = dp[i - 1][j - 1] + 1

最終取這三個操作的最小值即可。

程式碼:

class Solution {
public:
    int minDistance(string word1, string word2) {
        int len1 = word1.size(), len2 = word2.size();
        vector<vector<int>> dp(len1 + 1, vector<int>(len2 + 1));
        for(int i = 0; i <= len1; ++i) dp[i][0] = i;
        for(int j = 0; j <= len2; ++j) dp[0][j] = j;
        for(int i = 1; i <= len1; ++i) {
            for(int j = 1; j <= len2; ++j) {
                if(word1[i - 1] == word2[j - 1]) dp[i][j] = dp[i - 1][j - 1];
                else dp[i][j] = min({dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]}) + 1;
            }
        }
        return dp[len1][len2];
    }
};

相關文章