给定两个字符串s1, s2
,找到使两个字符串相等所需删除字符的ASCII值的最小和。
示例 1:
输入: s1 = "sea", s2 = "eat" 输出: 231 解释: 在 "sea" 中删除 "s" 并将 "s" 的值(115)加入总和。 在 "eat" 中删除 "t" 并将 116 加入总和。 结束时,两个字符串相等,115 + 116 = 231 就是符合条件的最小和。
示例 2:
输入: s1 = "delete", s2 = "leet" 输出: 403 解释: 在 "delete" 中删除 "dee" 字符串变成 "let", 将 100[d]+101[e]+101[e] 加入总和。在 "leet" 中删除 "e" 将 101[e] 加入总和。 结束时,两个字符串都等于 "let",结果即为 100+101+101+101 = 403 。 如果改为将两个字符串转换为 "lee" 或 "eet",我们会得到 433 或 417 的结果,比答案更大。
注意:
0 < s1.length, s2.length <= 1000
。- 所有字符串中的字符ASCII值在
[97, 122]
之间。
思路:这道题是DTW(动态时间规整)的题目,动态时间规整是为了解决两端序列长度不一致而求两端序列相似度的问题,如下图所示:
由于红色线相对于黑线往左偏移,如果直接用欧式距离表征差异性会得到很大的差异,但是其实两端序列是很相似的,所以我们需要一种方法来表征这种长度不一致求相似性的问题,就是动态时间规整。
动态时间规整通过维护一个二维数组dp来记录当前距离差,其中dp[i][j]表示A序列的第i个下标和B序列第j个下标的距离差,假设A序列有m个值,B序列有n个值,那么最后迭代的dp[m][n]就是我们得到的A和B序列的距离差,值越小表示越相似,递推方程模板如下:
其中,累积距离dist(i,j)为当前格点的距离d(A(i),B(j)),也就是两个序列A,B中的对应两点A(i)和B(j)的欧式距离与到达该点的最小的邻近元素的累积距离(dist(i-1,j-1),dist(i-1,j),dist(i,j-1))之和。那么我们来看针对这道问题如何求解呢?
像01背包那样理解即:s1[0:i]
s2[0:j]
中一共有i + j
个字符,从s1
中从左至右选取若干字符,s2
中从左至右选取若干相同字符。那么,那些未被选中的就是要删除的。再换个角度思考即:选取要删除的字符。
DP[i][j]
表示从s1 0-i, s2 0-j
中删除若干字符后,两个字符串相等时,删除的字符串的ASCII值的和的最小值。
那么一个O(mn)
的循环扫描中,假设我们已知了DP[i-1][j-1] = a; DP[i-1][j] = b; DP[i][j-1] = c
:
- 当
s1[i] == s2[j]
时:DP[i][j] = DP[i-1][j-1]
因为不需要再删除字符,保留s1[i],s2[j]
即可。 - 当
s1[i] != s2[j]
时:
此时,需要删除字符才能满足相等。
1)删除s1[i]
DP[i][j] = DP[i-1][j] + s1[i]
2)删除s2[j]
DP[i][j] = DP[i][j-1] + s2[j]
3)删除s1[i]
和s2[j]
DP[i][j] = DP[i-1][j-1] + s1[i] + s2[j]
实际上如果两个都要删除,说明s1[0:i] s2[0:j]
这些字符中都找不到s1[i],s2[j]
,那么DP[i-1][j]
中一定选中了s2[j]
进行删除,DP[i][j-1]
中一定选中了s1[i]
删除。那么,这种情况就和1)2)重合了,可以不必重复考虑。
因此DP[i][j] = min{DP[i-1][j] + s1[i], DP[i][j-1] + s2[j]}
Initialization
在上述算法中,假设的已知条件作为起点才能驱动整个循环。
首先DP[0][0] =s1[0] == s2[0] ? 0: s1[0] + s2[0];
- 初始化
DP[0][0-n]
即看做s1
只有一个字符,s2[0-n)
。假设s2
中第i
个字符与s1[0]
第一次相等(如不存在i = n),那么对于0 < j < n and j != i
都有DP[0][j] = DP[0][j-1] + s2[j]
当j == i
时DP[0][j] = DP[0][j-1] -s1[0]
(s1[0]
不需要再删除) - 初始化
DP[0-n][0]
同上
Example
s1 = se, s2 = ea
, 求DP[1][1] = ?
//i = 0, j = 0
DP[0][0] = s + e; //216
//i = 0, j = 1
DP[0][1] = DP[0][0] + a //s1[0] = s s2中没有s字符DP[0][j] = DP[0][j-1] + s2[j]
313
//i = 1, j = 0
DP[1][0] = DP[0][0] - s2[0]//s2[0] = e, s1中第一个为e的字符index = 1 DP[i][0] = DP[i-1][j] - s2[0]
115
//i = 1, j = 1
DP[1][1] = min{DP[0][1] + e, DP[1][0] + a} //s1[i] != s2[j] , min{删除e, 删除a}
min{414, 212} = 212
class Solution {
public:
int minimumDeleteSum(string s1, string s2) {
vector<vector<int>> dp(s1.size(), vector<int>(s2.size(), 10000));
dp[0][0] = s1[0] == s2[0] ? 0 : s1[0] + s2[0];
bool flag = dp[0][0] == 0 ? true : false;
for (int i = 1; i < s1.size(); i++) {
if (s1[i] == s2[0] && !flag) {
dp[i][0] = dp[i - 1][0] - s2[0];
flag = true;
}
else {
dp[i][0] = dp[i - 1][0] + s1[i];
}
}
flag= dp[0][0] == 0 ? true : false;
for (int i = 1; i < s2.size(); i++) {
if (s1[0] == s2[i] && !flag) {
dp[0][i] = dp[0][i - 1] - s1[0];
flag = true;
}
else {
dp[0][i] = dp[0][i - 1] + s2[i];
}
}
for (int i = 1; i < s1.size(); i++) {
for (int j = 1; j < s2.size(); j++) {
if (s1[i] == s2[j]) {
dp[i][j] = dp[i - 1][j - 1];
}
else {
dp[i][j] = min(dp[i - 1][j] + s1[i], dp[i][j - 1] + s2[j]);
}
}
}
return dp[s1.size() - 1][s2.size() - 1];
}
};
给定两个字符串s1, s2
,找到使两个字符串相等所需删除字符的ASCII值的最小和。
示例 1:
输入: s1 = "sea", s2 = "eat" 输出: 231 解释: 在 "sea" 中删除 "s" 并将 "s" 的值(115)加入总和。 在 "eat" 中删除 "t" 并将 116 加入总和。 结束时,两个字符串相等,115 + 116 = 231 就是符合条件的最小和。
示例 2:
输入: s1 = "delete", s2 = "leet" 输出: 403 解释: 在 "delete" 中删除 "dee" 字符串变成 "let", 将 100[d]+101[e]+101[e] 加入总和。在 "leet" 中删除 "e" 将 101[e] 加入总和。 结束时,两个字符串都等于 "let",结果即为 100+101+101+101 = 403 。 如果改为将两个字符串转换为 "lee" 或 "eet",我们会得到 433 或 417 的结果,比答案更大。
注意:
0 < s1.length, s2.length <= 1000
。- 所有字符串中的字符ASCII值在
[97, 122]
之间。