首页 > 其他分享 >Leetcocde 1092. 最短公共超序列

Leetcocde 1092. 最短公共超序列

时间:2024-01-26 22:55:25浏览次数:27  
标签:1092 s2 s1 最短 ans 字符串 Leetcocde str1 dp

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

[我的视频题解空间](https://space.bilibili.com/18508846)

标签:1092,s2,s1,最短,ans,字符串,Leetcocde,str1,dp
From: https://www.cnblogs.com/itdef/p/17990811

相关文章

  • dijkstra算法(单源最短路径)
    单源最短路径是求解给定一个起点到其他所有的点的最短距离。适用的范围:有向图、边的权值没有负数洛谷测试链接代码实现#define_CRT_SECURE_NO_WARNINGS#include<stdio.h>#include<stdlib.h>#include<string.h>#include<math.h>#include<time.h>#include<stdboo......
  • 图的最短路-Dijkstra 详解
    Dijkstra  概念注意一下,Dijkstra不适用于有负边权的图   就是刚开始一些点(集合B),把排序的结果放入集合A,先确定起点0,然后找集合B里面的最小值,这样才可以确定你修改的这个点的最短路在目前是最优解(后期可能改动),因为集合A的都是确定好了的最短路,所以集合A的数不做修......
  • 分布图最短路径算法比较
    用户维护好仓区的点和线,生成分布图时,用户任意选取两个点,后端求出当前最短路径。假设图G(m,n),m个顶点,n条边算法对比:floyd算法时间复杂度o(m3)缺点:时间复杂度过高dijkstra算法时间复杂度o(m2),使用优先队列可以降到o(m*logm)邻接矩阵存储:适合稠密图邻接表存储:适合稀疏图缺点:不适......
  • Johnson 全源最短路算法
    ​ Johnson全源最短路是一种允许带负权边的全源最短路算法。它的主要实现思路即为将原先带负权边的图转化成求一个无负权边的图的全源最短路。​ 我们定义一个新节点\(0\),其中\(0\)节点与其它各节点连接一条边权为\(0\)的边。令\(h_i\)为\(0\)节点到\(i\)节点的最短......
  • 负债 1092.8 亿美元,苹果成全球负债第二多的科技公司丨 RTE 开发者日报 Vol.131
       开发者朋友们大家好: 这里是「RTE开发者日报」,每天和大家一起看新闻、聊八卦。我们的社区编辑团队会整理分享RTE(RealTimeEngagement)领域内「有话题的新闻」、「有态度的观点」、「有意思的数据」、「有思考的文章」、「有看点的会议」,但内容仅代表......
  • C++U6-03-最短路算法4-floyd算法
    B站复习视频:1、https://www.bilibili.com/video/BV1Fj411d71S/?spm_id_from=333.999.0.02、https://www.bilibili.com/video/BV1RK4y1d7ct?p=1&vd_source=5c960e1ede940bc5cab8ed42c8bdc937学习目标 floyd算法Floyd算法是一种用于找到图中所有节点对之间最短路径的动态规划......
  • C++U6-03-最短路算法2-bellmon-ford算法
    学习目标贝尔曼福特算法、SPFA 可以用来复习的B站视频:1、https://www.bilibili.com/video/BV1RK4y1d7ct?p=3&vd_source=5c960e1ede940bc5cab8ed42c8bdc9372、https://www.bilibili.com/video/BV18a4y1A7gv/?spm_id_from=333.999.0.0 SPFA算法是 Bellman-Ford算法 的队......
  • CF-720-E- 最短路+最小生成树
    1051-F题目大意给定一个\(n\)个点\(m\)条边的无向联通图,边带权。有\(q\)次询问,每次询问两点\(x,y\)直接的最短路的长度。Solution注意到\(m-n{\le}20\),那么整个图可以视为一个生成树加上不超过\(21\)条非树边构成的图,这些非树边构成一个边集\(E\)。先把整个图的最小生成树搞......
  • 1.同余最短路
    省选模拟赛T3一小部分用到了同余最短路,发现这简单东西自己从来没学过,补一下。\(n\)个正整数,分别为\(A_1,A_2,\cdots,A_n\),求\([0,V]\)中有多少个数可以被表示为\(\sum\limits_{i=1}^{n}A_ix_i,x_i\in\mathbb{N}\)。可以完全背包,但复杂度\(O(nV)\),当\(V\)很大的时候......
  • C++U6-02-最短路算法1-dijkstra迪杰斯特拉最短路径
    学习目标 最短路径的基本概念  练习1 最短路的定义 本节课迪杰斯特拉dijkstra最短路算法 算法流程:以下是Dijkstra最短路径算法的逐步计算松弛的过程:初始化起始节点的距离为0,其他节点的距离为无穷大。选择当前距离最小且未被访问的节点作为当前节点。......