Medium
题目描述
给定两个字符串 s1 和 s2,返回使两个字符串相等所需删除字符的 ASCII 值的最小和。
示例 1:
输入:s1 = "sea", s2 = "eat"
输出:231
解释:在 "sea" 中删除 "s" 并将其 ASCII 值 115 加到总和中。
在 "eat" 中删除 "t" 并将其 ASCII 值 116 加到总和中。
结束时,两个字符串相等,115 + 116 = 231 是可能的最小和。
示例 2:
输入:s1 = "delete", s2 = "leet"
输出:403
解释:在 "delete" 中删除 "dee" 得到 "let",
将 100[d] + 101[e] + 101[e] 加到总和中。
在 "leet" 中删除 "e" 得到 "let",将 101[e] 加到总和中。
结束时,两个字符串都等于 "let",答案为 100+101+101+101 = 403。
如果改为将两个字符串都转换为 "lee" 或 "eet",我们会得到 433 或 417 的答案,比这更大。
提示:
1 <= s1.length, s2.length <= 1000s1和s2由小写英文字母组成
解题思路
这是一个经典的动态规划问题,可以看作是最长公共子序列(LCS)的变形。
核心思路: 我们需要找到两个字符串的最大公共子序列,然后删除不在公共子序列中的字符。但这里不是求长度,而是求ASCII值的最小删除和。
状态定义:
dp[i][j] 表示使 s1[0...i-1] 和 s2[0...j-1] 相等所需的最小ASCII删除和。
状态转移:
如果
s1[i-1] == s2[j-1],字符相同,不需要删除:dp[i][j] = dp[i-1][j-1]如果字符不同,有两种选择:
- 删除
s1[i-1]:dp[i][j] = dp[i-1][j] + s1[i-1] - 删除
s2[j-1]:dp[i][j] = dp[i][j-1] + s2[j-1]取两者中的最小值。
- 删除
边界条件:
dp[0][j]:s1为空,需要删除s2的前j个字符dp[i][0]:s2为空,需要删除s1的前i个字符
时间复杂度O(mn),空间复杂度O(mn),其中m、n分别是两个字符串的长度。
代码实现
class Solution {
public:
int minimumDeleteSum(string s1, string s2) {
int m = s1.length(), n = s2.length();
vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0));
// 初始化边界条件
for (int i = 1; i <= m; i++) {
dp[i][0] = dp[i-1][0] + s1[i-1];
}
for (int j = 1; j <= n; j++) {
dp[0][j] = dp[0][j-1] + s2[j-1];
}
// 填充dp表
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if (s1[i-1] == s2[j-1]) {
dp[i][j] = dp[i-1][j-1];
} else {
dp[i][j] = min(dp[i-1][j] + s1[i-1],
dp[i][j-1] + s2[j-1]);
}
}
}
return dp[m][n];
}
};
class Solution:
def minimumDeleteSum(self, s1: str, s2: str) -> int:
m, n = len(s1), len(s2)
dp = [[0] * (n + 1) for _ in range(m + 1)]
# 初始化边界条件
for i in range(1, m + 1):
dp[i][0] = dp[i-1][0] + ord(s1[i-1])
for j in range(1, n + 1):
dp[0][j] = dp[0][j-1] + ord(s2[j-1])
# 填充dp表
for i in range(1, m + 1):
for j in range(1, n + 1):
if s1[i-1] == s2[j-1]:
dp[i][j] = dp[i-1][j-1]
else:
dp[i][j] = min(dp[i-1][j] + ord(s1[i-1]),
dp[i][j-1] + ord(s2[j-1]))
return dp[m][n]
public class Solution {
public int MinimumDeleteSum(string s1, string s2) {
int m = s1.Length, n = s2.Length;
int[,] dp = new int[m + 1, n + 1];
// 初始化边界条件
for (int i = 1; i <= m; i++) {
dp[i, 0] = dp[i - 1, 0] + s1[i - 1];
}
for (int j = 1; j <= n; j++) {
dp[0, j] = dp[0, j - 1] + s2[j - 1];
}
// 填充dp表
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if (s1[i - 1] == s2[j - 1]) {
dp[i, j] = dp[i - 1, j - 1];
} else {
dp[i, j] = Math.Min(dp[i - 1, j] + s1[i - 1],
dp[i, j - 1] + s2[j - 1]);
}
}
}
return dp[m, n];
}
}
/**
* @param {string} s1
* @param {string} s2
* @return {number}
*/
var minimumDeleteSum = function(s1, s2) {
const m = s1.length;
const n = s2.length;
// dp[i][j] represents min delete sum to make s1[0...i-1] and s2[0...j-1] equal
const dp = Array(m + 1).fill().map(() => Array(n + 1).fill(0));
// Fill first row - delete all characters from s1
for (let i = 1; i <= m; i++) {
dp[i][0] = dp[i-1][0] + s1.charCodeAt(i-1);
}
// Fill first column - delete all characters from s2
for (let j = 1; j <= n; j++) {
dp[0][j] = dp[0][j-1] + s2.charCodeAt(j-1);
}
// Fill the rest of the dp table
for (let i = 1; i <= m; i++) {
for (let j = 1; j <= n; j++) {
if (s1[i-1] === s2[j-1]) {
// Characters match, no deletion needed
dp[i][j] = dp[i-1][j-1];
} else {
// Take minimum of deleting from s1 or s2
dp[i][j] = Math.min(
dp[i-1][j] + s1.charCodeAt(i-1), // delete from s1
dp[i][j-1] + s2.charCodeAt(j-1) // delete from s2
);
}
}
}
return dp[m][n];
};
复杂度分析
| 复杂度类型 | 复杂度 | 说明 |
|---|---|---|
| 时间复杂度 | O(m×n) | 需要填充m×n的二维数组,每个位置计算为O(1) |
| 空间复杂度 | O(m×n) | 使用二维数组存储动态规划状态 |
注:m和n分别为字符串s1和s2的长度
相关题目
. Edit Distance (Medium)
. Longest Increasing Subsequence (Medium)
. Delete Operation for Two Strings (Medium)