1. 题目链接
2. 题目描述
给你两个字符串 str1
和 str2
,返回同时以 str1
和 str2
作为 子序列 的最短字符串。如果答案不止一个,则可以返回满足条件的 任意一个 答案。
如果从字符串 t
中删除一些字符(也可能不删除),可以得到字符串 s
,那么 s
就是 t 的一个子序列。
3. 题目示例
示例 1 :
输入:str1 = "abac", str2 = "cab"
输出:"cabac"
解释:
str1 = "abac" 是 "cabac" 的一个子串,因为我们可以删去 "cabac" 的第一个 "c"得到 "abac"。
str2 = "cab" 是 "cabac" 的一个子串,因为我们可以删去 "cabac" 末尾的 "ac" 得到 "cab"。
最终我们给出的答案是满足上述属性的最短字符串。
示例 2 :
输入:str1 = "aaaaaaaa", str2 = "aaaaaaaa"
输出:"aaaaaaaa"
4. 解题思路
核心思路:动态规划(DP)
- DP 状态定义:
f[i][j]
表示str1
的前i
个字符和str2
的前j
个字符的最短公共超序列的长度。- 最终目标是计算
f[n][m]
,其中n = str1.length
,m = str2.length
。
- 状态转移方程:
- 如果
str1[i-1] == str2[j-1]
,则f[i][j] = f[i-1][j-1] + 1
(当前字符相同,直接加入超序列)。 - 否则,
f[i][j] = min(f[i-1][j], f[i][j-1]) + 1
(选择更短的路径,并加上当前字符)。
- 如果
- 回溯构造结果:
- 从
f[n][m]
开始,逆向追踪 DP 表,构造最短公共超序列。 - 如果
str1[i] == str2[j]
,则当前字符必须加入结果。 - 否则,根据
f[i][j]
的来源决定是取str1[i]
还是str2[j]
。
- 从
5. 题解代码
class Solution {
public String shortestCommonSupersequence(String str1, String str2) {
// 转换为字符数组方便处理
char[] s = str1.toCharArray(), t = str2.toCharArray();
int n = s.length, m = t.length;
// f[i+1][j+1] 表示 s 的前 i 个字母和 t 的前 j 个字母的最短公共超序列的长度
var f = new int[n + 1][m + 1];
// 边界条件:如果其中一个字符串为空,最短公共超序列就是另一个字符串
for (int j = 1; j <= m; ++j) f[0][j] = j;
for (int i = 1; i <= n; ++i) f[i][0] = i;
// 填充 DP 表
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
if (s[i] == t[j]) {
// 当前字符相同,直接加入超序列
f[i + 1][j + 1] = f[i][j] + 1;
} else {
// 否则,选择更短的路径
f[i + 1][j + 1] = Math.min(f[i][j + 1], f[i + 1][j]) + 1;
}
}
}
// 回溯构造结果
int na = f[n][m]; // 最短公共超序列的长度
var ans = new char[na]; // 存储最终结果
for (int i = n - 1, j = m - 1, k = na - 1; ; ) {
if (i < 0) { // str1 已经处理完,剩下的 str2 直接加入
System.arraycopy(t, 0, ans, 0, j + 1);
break;
}
if (j < 0) { // str2 已经处理完,剩下的 str1 直接加入
System.arraycopy(s, 0, ans, 0, i + 1);
break;
}
if (s[i] == t[j]) { // 当前字符相同,加入结果
ans[k--] = s[i--];
--j;
} else if (f[i + 1][j + 1] == f[i][j + 1] + 1) { // 来自上方(str1)
ans[k--] = s[i--];
} else { // 来自左方(str2)
ans[k--] = t[j--];
}
}
return new String(ans);
}
}
6. 复杂度分析
- 时间复杂度:
O(n*m)
,其中n = str1.length
,m = str2.length
。- DP 表填充需要
O(n*m)
。 - 回溯构造结果需要
O(n + m)
。
- DP 表填充需要
- 空间复杂度:
O(n*m)
,用于存储 DP 表。