【LeetCode】1092. 最短公共超序列(hard)——最长公共子序列

【题目链接】

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

【题目大意】

给定两个字符串 s t r 1 str1 str1 s t r 2 str2 str2 ,求二者的最短公共超序列。
公共超序列的定义与公共子序列相反,即要求一个最短的序列,使得 s t r 1 str1 str1 s t r 2 str2 str2 均是其子序列。
注:答案不唯一,只要构造出来的序列满足条件即可,不要对着预期结果一直debug(说的就是我自己)。

【输入示例】

输入:str1 = “abac”, str2 = “cab”
输出:“cabac”
解释:
str1 = “abac” 是 “cabac” 的一个子串,因为我们可以删去 “cabac” 的第一个 "c"得到 “abac”。
str2 = “cab” 是 “cabac” 的一个子串,因为我们可以删去 “cabac” 末尾的 “ac” 得到 “cab”。
最终我们给出的答案是满足上述属性的最短字符串。

【数据范围】

1 <= str1.length, str2.length <= 1000
str1 和 str2 都由小写英文字母组成。


最长公共子序列+构造

  其实想到方法并不难,难点在于怎么实现出来。从题意就能想到,既然 s t r 1 str1 str1 s t r 2 str2 str2 是这个超序列的子串,那我们先求出 s t r 1 str1 str1 s t r 2 str2 str2 的最长公共子序列,然后把公共的部分放进结果字符串,再把不公共的部分全塞进去不就好了。然后问题就来了,子序列不同于子串。子串是连续的,我们可以直接拼接。但是子序列是可以不连续的,怎么放才能保证这个超序列具有 s t r 1 str1 str1 s t r 2 str2 str2 这两个子序列呢?答案是从最长公共子序列里面的数组反推。
  最长公共子序列的递推方程是 d p [ i ] [ j ] = { 0 i = 0  or  j = 0 d p [ i − 1 ] [ j − 1 ] + 1 s t r 1 [ i − 1 ] = s t r 2 [ j − 1 ] max ⁡ ( d p [ i − 1 ] [ j ] , d p [ i ] [ j − 1 ] ) s t r 1 [ i − 1 ] ≠ s t r 2 [ j − 1 ] dp[i][j]=\begin{cases}0&i=0\text{ or }j=0\\ dp[i-1][j-1]+1&str1[i-1]=str2[j-1]\\ \max(dp[i-1][j],dp[i][j-1])&str1[i-1]\neq str2[j-1]\end{cases} dp[i][j]= 0dp[i1][j1]+1max(dp[i1][j],dp[i][j1])i=0 or j=0str1[i1]=str2[j1]str1[i1]=str2[j1]
跑完这个动态规划数组之后,我们用双指针 i i i j j j 倒着往前推

  • 如果 d p [ i ] [ j ] = d p [ i − 1 ] [ j − 1 ] + 1 dp[i][j]=dp[i-1][j-1]+1 dp[i][j]=dp[i1][j1]+1 ,说明当前字符是公共的部分,我们将 s t r 1 [ i ] str1[i] str1[i] s t r 2 [ j ] str2[j] str2[j] 加入结果字符串
  • 如果 d p [ i ] [ j ] = d p [ i − 1 ] [ j ] dp[i][j]=dp[i-1][j] dp[i][j]=dp[i1][j],说明 s t r 1 [ i ] str1[i] str1[i] 是对最长公共子序列没有贡献的,也即不一样的部分,因此将 s t r 1 [ i ] str1[i] str1[i] 加入
  • 同理,如果 d p [ i ] [ j ] = d p [ i ] [ j − 1 ] dp[i][j]=dp[i][j-1] dp[i][j]=dp[i][j1],将 s t r 2 [ j ] str2[j] str2[j] 加入

同时还要考虑边界的问题,因为二者有可能不一样长,在其中一方比对结束之后,另一方剩余字符直接加在末尾。

class Solution {
public:
    string shortestCommonSupersequence(string str1, string str2) {
        int m=str1.length(),n=str2.length();
        vector<vector<int>> dp(m+1,vector<int> (n+1,0));
        //求最长公共子序列
        for(int i=1;i<=m;++i){
            for(int j=1;j<=n;++j){
                if(str1[i-1]==str2[j-1]) dp[i][j]=dp[i-1][j-1]+1;
                else dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
            }
        }
        int i=m,j=n;
        string res;
        while(i||j){
            if(i==0) res=str2[--j]+res;//str1已结束
            else if(j==0) res=str1[--i]+res;//str2已结束
            else{
            //分别对应上述的三种情况
                if(dp[i][j]==dp[i-1][j]) res=str1[--i]+res;
                else if(dp[i][j]==dp[i][j-1]) res=str2[--j]+res;
                else res=str1[--i]+res,--j;
            }
        }
        return res;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值