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;
    }
};

我的视频题解空间

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