来源:力扣(LeetCode)
描述:
给出两个字符串 str1
和 str2
,返回同时以 str1
和 str2
作为子序列的最短字符串。如果答案不止一个,则可以返回满足条件的任意一个答案。
(如果从字符串 T 中删除一些字符(也可能不删除,并且选出的这些字符可以位于 T 中的 任意位置),可以得到字符串 S,那么 S 就是 T 的子序列)
示例:
输入: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 都由小写英文字母组成。
方法:动态规划
思路与算法
首先题目给出两个字符串 str1 和 str2 ,我们需要返回任意一个满足同时以 str1 和 str2 作为子序列的最短字符串。我们设 str1 的长度为 n,str2 的长度为 m, dp[i][j] 表示同时以字符串 str1[i:n] 和 str2[j:m] 作为子序列的最短字符串长度,其中 str1[i:j],str2[i : j] 表示字符串 str1,str2 从下标 i 到下标 j 的子串(包含下标 i 且不包含下标 j)。现在我们来思考如何求解各个状态:
- 当 str1[i] = str2[j]时,以 str1[i : n] 和 str2[j : m] 作为子序列的最短字符串的开头字符为 str1 [i] 一定是最优的:
- 否则当 str1[i] ≠ str2[j] 时,以 str1[i : n] 和 str2[j : m] 作为子序列的最短字符串的开头字符只能为 str1[i] 或者 str2[j]:
上文讨论的是建立在 i < n 和 j < m 的前提上的,我们还需要考虑动态规划的边界条件,当 i = n 或 j = m 时,此时其中一个字符串为空串,同时满足以两字符串作为子序列的最短字符串长度为另一个字符串的长度。因此我们就可以写出动态规划的边界条件:
可以看到每一个区间上的求解都与其子区间的求解有关,所以我们可以采用「自底向上」的编码方式来实现求解过程。求解完毕后,此时 dp[0][0] 即为此时题目中满足同时以 str1 和 str2 作为子序列的最短字符串长度,然后我们可以通过双指针和通过动态规划状态来从前往后构造出具体方案。我们用指针 t1 = 0 来指向 str1 的头部,用 t2 = 0 来指向 str2 的头部,并根据 dp[i][j] 来从前往后构造出具体字符串方案:
- 若 t1 到达 str1 尾部(t1 = n)或者 t2 到达 str2 尾部(t2 = m),将对应的剩下的字符加到结果字符串后面。
- 否则:
- 若 str1[t1] = str2[t2],则此时结果字符串最后添加 str1[t1] 为最优条件,然后 t1
和 t2 指针分别往后移动一单位。 - 否则若当
- 若 str1[t1] = str2[t2],则此时结果字符串最后添加 str1[t1] 为最优条件,然后 t1
向结果字符串中添加 str1 [t1],然后 t1 指针往后移动一单位。若当
则向结果字符串中添加 str2[t2],然后 t2 指针往后移动一单位。由于我们只需要返回任一符合条件的结果字符串,所以若条件 (1)(2) 都满足我们只需要取其中一种情况即可。
代码:
class Solution {
public:
string shortestCommonSupersequence(string str1, string str2) {
int n = str1.size(), m = str2.size();
vector<vector<int>> dp(n + 1, vector<int>(m + 1));
for (int i = 0; i < n; ++i) {
dp[i][m] = n - i;
}
for (int i = 0; i < m; ++i) {
dp[n][i] = m - i;
}
for (int i = n - 1; i >= 0; --i) {
for (int j = m - 1; j >= 0; --j) {
if (str1[i] == str2[j]) {
dp[i][j] = dp[i + 1][j + 1] + 1;
} else {
dp[i][j] = min(dp[i + 1][j], dp[i][j + 1]) + 1;
}
}
}
string res;
int t1 = 0, t2 = 0;
while (t1 < n && t2 < m) {
if (str1[t1] == str2[t2]) {
res += str1[t1];
++t1;
++t2;
} else if (dp[t1 + 1][t2] == dp[t1][t2] - 1) {
res += str1[t1];
++t1;
} else if (dp[t1][t2 + 1] == dp[t1][t2] - 1) {
res += str2[t2];
++t2;
}
}
if (t1 < n) {
res += str1.substr(t1);
}
else if (t2 < m) {
res += str2.substr(t2);
}
return res;
}
};
执行用时:20 ms, 在所有 C++ 提交中击败了75.29%的用户
内存消耗:12.5 MB, 在所有 C++ 提交中击败了80.59%的用户
复杂度分析
时间复杂度:O(n×m)。其中预处理动态规划求解的复杂度为 O(n×m),构造具体字符串方案的复杂度为 O(n+m),其中 n 为字符串 str1 的长度,m 为字符串 str2 的长度。
空间复杂度:O(n×m),其中 n 为字符串 str1 的长度,m 为字符串 str2 的长度。空间复杂度主要取决于动态规划模型中状态的总数。
author:LeetCode-Solution