【题目链接】
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[i−1][j−1]+1max(dp[i−1][j],dp[i][j−1])i=0 or j=0str1[i−1]=str2[j−1]str1[i−1]=str2[j−1]
跑完这个动态规划数组之后,我们用双指针
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[i−1][j−1]+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[i−1][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][j−1],将 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;
}
};