Leetcocde 1092. 最短公共超序列

https://leetcode.cn/problems/shortest-common-supersequence/description/

給你兩個字符串 str1 和 str2,返回同時以 str1 和 str2 作爲 子序列 的最短字符串。如果答案不止一個,則可以返回滿足條件的 任意一個 答案。
如果從字符串 t 中刪除一些字符(也可能不刪除),可以得到字符串 s ,那麼 s 就是 t 的一個子序列。

示例 1:
輸入:str1 = "abac", str2 = "cab"
輸出:"cabac"
解釋:
str1 = "abac" 是 "cabac" 的一個子串,因爲我們可以刪去 "cabac" 的第一個 "c"得到 "abac"。 
str2 = "cab" 是 "cabac" 的一個子串,因爲我們可以刪去 "cabac" 末尾的 "ac" 得到 "cab"。
最終我們給出的答案是滿足上述屬性的最短字符串。

示例 2:
輸入:str1 = "aaaaaaaa", str2 = "aaaaaaaa"
輸出:"aaaaaaaa"
 
提示:
1 <= str1.length, str2.length <= 1000
str1 和 str2 都由小寫英文字母組成。

解答
本題使用dp解答。 有兩種方案:
1 公共子序列
2 編輯距離

先看第一種方案 公共子序列

dp[i][j] 表示能包含str1[1~i]和str2[1~j]的字符串作爲子序列的答案字符串最短長度。
那麼起始值  dp[i][0]=i;  dp[0][i] =i; 也就是包含空串和一個strx字符串作爲子序列的答案字符串最短長度自然就是那個strx字符串的長度

另一個難點 從dp中推導正確的字符串。
由上圖可知 dp[i][j] 肯定是從dp[i-1][j-1] dp[i-1][j] dp[i][j-1]轉化而來
也代表選擇某個字母 那麼我們通過比較即可知道應該選擇哪個字符串的哪個字母。
我們從後到前推導
當str1[i] ==str2[j]時,都是同一個字母,那麼毫無懸念,選擇該字母加入答案字符串. 兩個字符串的索引向前移動一位
當str1[i] !=str2[j]時,根據dp的比較 確定選擇哪個字符串的哪個字母, 然後做出改動的字符串的索引向前移動一位
由於我們從後到前推到 所以得到的公共部分需要翻轉.
當str1 或者str2的字母全部進入答案字符串,也就是一個字符串此時爲空另一個字符串有剩餘的字母沒處理,
那麼把剩餘的字符串全部加入到答案字符串的前面即可(因爲我們是從後到前推導的)
那麼得到正確dp答案的代碼如下

class Solution {
public:
    vector<vector<int>> dp;
    string shortestCommonSupersequence(string s1, string s2) {
        dp.resize(1010, vector<int>(1010, 0x3f3f3f3f)); //初始化dp
        //字符串插入字符 dp從1索引開始 避免考慮邊界問題
        s1.insert(s1.begin(), '#');  s2.insert(s2.begin(),'@');
        //初始化dp起始值
        //dp[i][j] 表示能包含str1[1~i]和str2[1~j]的字符串作爲子序列的答案字符串最短長度。
        //那麼起始值  dp[i][0]=i;  dp[0][i] =i; 
        //也就是包含空串和一個strx字符串作爲子序列的答案字符串最短長度自然就是那個strx字符串的長度
        for (int i = 0; i < s1.size(); i++) { dp[i][0] = i; }
        for (int j = 0; j < s2.size(); j++) { dp[0][j] = j; }
        //狀態轉移
        for (int i = 1; i < s1.size(); i++) {
            for (int j = 1; j < s2.size(); j++) {
                if (s1[i] == s2[j]) {
                    dp[i][j] = dp[i - 1][j - 1] + 1;
                }
                else {
                    dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]) + 1;
                }
            }
        }
        //由dp推導出字符串
        string ans;
        int a = s1.size() - 1; int b = s2.size() - 1;
        //由於dp[i][j] 與dp[i-1][j-1] dp[i-1][j] dp[i][j-1]的變化 
        //確定是選擇哪個字符串中的哪個字母
        while (a > 0 && b > 0) {
            if (s1[a] == s2[b]) {
                ans += s1[a];
                a--; b--;
            }
            else if (dp[a][b] == dp[a - 1][b] + 1) {
                ans += s1[a]; a--;
            }
            else if (dp[a][b] == dp[a][b - 1] + 1) {
                ans += s2[b]; b--;
            }
        }
        //由於是從後到前選擇的 所以需要翻轉一下字符串
        reverse(ans.begin(), ans.end());
        //兩個字符串有一個全部添加了 那麼把剩餘的另一個字符串全部添加到答案前面即可
        if (a > 0) {
            ans = s1.substr(1,a) + ans;
        }
        if (b > 0) {
            ans = s2.substr(1,b) + ans;
        }

        return ans;
    }
};

第二種方案 編輯距離

dp[i][j] 表示往str1[1~i]的部分添加字母 使得其子序列能包含str2[1~j]的組合, 所添加操作的最小次數
那麼起始值  dp[i][0]=0; str1[1~i]包含空字符串爲子序列,所以無須操作,操作次數爲0
 dp[0][i] =i; 如果str1爲空字符串 如果想讓其子序列包含str2[1~j] 那麼需要添加j個字母,操作次數爲j.

從dp中推導出正確的字符串和上面的基本一致,不在重複描述了
代碼如下

class Solution {
public:
    vector<vector<int>> dp;
    string shortestCommonSupersequence(string s1, string s2) {
        dp.resize(1010, vector<int>(1010, 0x3f3f3f3f));//初始化dp
        //字符串插入字符 dp從1索引開始 避免考慮邊界問題
        s1.insert(s1.begin(), '#');  s2.insert(s2.begin(), '@');
         //初始化dp起始值
        //dp[i][j] 表示往str1[1~i]的部分添加字母 使得其子序列能包含str2[1~j]的組合, 
        //所添加操作的最小次數
        //那麼起始值  dp[i][0]=0; str1[1~i]包含空字符串爲子序列,所以無須操作,操作次數爲0
        //dp[0][i] =i; 如果str1爲空字符串 如果想讓其子序列包含str2[1~j] 
        //那麼需要添加j個字母,操作次數爲j.
        for (int i = 0; i < s1.size(); i++) dp[i][0] = 0;
        for (int i = 0; i < s1.size(); i++) dp[0][i] = i;
        //轉移方程  注意兩個字母不同時候的轉移。
        //往str1[1~i-1]的部分添加字母 使得其子序列能包含str2[1~j]的組合, 
        //所添加操作的最小次數就等於
        //往str1[1~i]的部分添加字母 使得其子序列能包含str2[1~j]的組合,
        //所添加操作的最小次數
        for (int i = 1; i < s1.size(); i++) {
            for (int j = 1; j < s2.size(); j++) {
                if (s1[i] == s2[j]) {
                    dp[i][j] = dp[i - 1][j - 1];
                }
                else {
                    dp[i][j] = min(dp[i][j - 1]+1, dp[i - 1][j]);
                }
            }
        }
        //從dp倒推出答案字符串
        string ans;
        int a = s1.size() - 1; int b = s2.size() - 1;
        while (a > 0 && b > 0) {
            if (s1[a] == s2[b] && dp[a][b]==dp[a-1][b-1]) {
                ans += s1[a]; a--; b--;
            }
            else if (dp[a][b] == dp[a][b - 1] + 1) {
                ans += s2[b]; b--;
            }
            else {
                ans += s1[a]; a--;
            }
        }

        reverse(ans.begin(), ans.end());
        if (a > 0) {
            ans = s1.substr(1, a) + ans;
        }

        if (b > 0) {
            ans = s2.substr(1, b) + ans;
        }

        return ans;
    }
};

我的視頻題解空間

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章