最长公共子序列的变种:最小ASCII删除和
题目描述
给定两个字符串s1和s2,我们需要通过删除一些字符(可以是0个)使得两个字符串相等。每次删除操作会删除一个字符,并且这个字符的ASCII值会被计入“删除成本”。我们的目标是找到使两个字符串相等所需的最小ASCII删除和。
换句话说,我们需要找到两个字符串的一个公共子序列,使得这个公共子序列之外的字符的ASCII值之和最小。这个和就是最小的ASCII删除和。
示例
输入: s1 = "sea", s2 = "eat"
输出: 231
解释:
- 删除 "s" (ASCII 115) 和 "t" (ASCII 116)
- 剩余字符串 "ea" 是公共子序列
- 总成本 = 115 + 116 = 231
解题思路
这个问题是经典的最长公共子序列(LCS)问题的变种。在LCS问题中,我们寻找最长的公共子序列。而在这里,我们关心的是那些“不属于”公共子序列的字符的ASCII值之和。
-
核心转换:最小删除成本,等价于两个字符串的总ASCII和,减去公共子序列的最大ASCII和。
- 总成本 =
sum(ASCII(s1)) + sum(ASCII(s2)) - 如果我们能找到一个公共子序列
LCS,其ASCII和为sum(ASCII(LCS)),那么我们需要删除的字符的ASCII和就是总成本 - 2 * sum(ASCII(LCS))。为什么是2倍?因为LCS同时存在于s1和s2中,我们保留它,就意味着我们不需要删除s1和s2中构成LCS的那些字符。我们删除的是s1中不属于LCS的字符和s2中不属于LCS的字符。 - 因此,最小化删除和 等价于 最大化公共子序列的ASCII和。
- 总成本 =
-
动态规划定义:我们定义一个二维DP数组。
- 设
dp[i][j]表示字符串s1的前i个字符(即s1[0...i-1])和字符串s2的前j个字符(即s2[0...j-1])所形成的公共子序列的最大ASCII和。 - 我们的最终目标是
dp[m][n],其中m和n分别是s1和s2的长度。
- 设
-
状态转移方程:我们如何推导出
dp[i][j]?- 基础情况:
- 如果
i == 0或j == 0,意味着其中一个字符串是空的。空字符串和任何字符串的公共子序列的ASCII和自然是0。所以dp[0][j] = 0对于所有j,dp[i][0] = 0对于所有i。
- 如果
- 一般情况 (
i > 0且j > 0):- 情况1:如果
s1[i-1] == s2[j-1](当前考虑的最后一个字符相等)- 这个相等的字符必然属于公共子序列,并且能为我们贡献它的ASCII值。
- 那么,
dp[i][j]就等于在剩下的字符串(s1的前i-1个字符和s2的前j-1个字符)中找到的公共子序列的最大ASCII和,再加上这个相等字符的ASCII值。 - 转移方程:
dp[i][j] = dp[i-1][j-1] + int(s1[i-1])
- 情况2:如果
s1[i-1] != s2[j-1](当前考虑的最后一个字符不相等)- 那么,这两个字符不可能同时出现在当前的公共子序列中。我们有两种选择:
- 选择A:放弃
s1的最后一个字符(即s1[i-1])。那么问题就转化为求s1的前i-1个字符和s2的前j个字符的公共子序列最大ASCII和,即dp[i-1][j]。 - 选择B:放弃
s2的最后一个字符(即s2[j-1])。那么问题就转化为求s1的前i个字符和s2的前j-1个字符的公共子序列最大ASCII和,即dp[i][j-1]。
- 选择A:放弃
- 我们的目标是最大化ASCII和,所以我们应该在这两种选择中取最大值。
- 转移方程:
dp[i][j] = max(dp[i-1][j], dp[i][j-1])
- 那么,这两个字符不可能同时出现在当前的公共子序列中。我们有两种选择:
- 情况1:如果
- 基础情况:
-
计算最终结果:
- 按照上述方程填充整个
(m+1) x (n+1)的DP表。通常我们使用双重循环,i从0到m,j从0到n。 - 计算两个字符串的总ASCII和:
total_ascii = sum(ord(c) for c in s1) + sum(ord(c) for c in s2) - 最小的ASCII删除和 =
total_ascii - 2 * dp[m][n]
- 按照上述方程填充整个
循序渐进的计算过程(以示例 s1="sea", s2="eat" 为例)
-
初始化:
m = 3,n = 3- 创建
dp表,大小为4x4(索引从0到3)。第一行和第一列初始化为0。 s1的ASCII和:'s'=115, 'e'=101, 'a'=97-> 总和 = 115+101+97 = 313s2的ASCII和:'e'=101, 'a'=97, 't'=116-> 总和 = 101+97+116 = 314total_ascii = 313 + 314 = 627
-
填充DP表:
-
i=1, j=1: 比较s1[0]='s'和s2[0]='e'- 不相等 ->
dp[1][1] = max(dp[0][1], dp[1][0]) = max(0, 0) = 0
- 不相等 ->
-
i=1, j=2: 比较s1[0]='s'和s2[1]='a'- 不相等 ->
dp[1][2] = max(dp[0][2], dp[1][1]) = max(0, 0) = 0
- 不相等 ->
-
i=1, j=3: 比较s1[0]='s'和s2[2]='t'- 不相等 ->
dp[1][3] = max(dp[0][3], dp[1][2]) = max(0, 0) = 0
- 不相等 ->
-
i=2, j=1: 比较s1[1]='e'和s2[0]='e'- 相等 ->
dp[2][1] = dp[1][0] + int('e') = 0 + 101 = 101
- 相等 ->
-
i=2, j=2: 比较s1[1]='e'和s2[1]='a'- 不相等 ->
dp[2][2] = max(dp[1][2], dp[2][1]) = max(0, 101) = 101
- 不相等 ->
-
i=2, j=3: 比较s1[1]='e'和s2[2]='t'- 不相等 ->
dp[2][3] = max(dp[1][3], dp[2][2]) = max(0, 101) = 101
- 不相等 ->
-
i=3, j=1: 比较s1[2]='a'和s2[0]='e'- 不相等 ->
dp[3][1] = max(dp[2][1], dp[3][0]) = max(101, 0) = 101
- 不相等 ->
-
i=3, j=2: 比较s1[2]='a'和s2[1]='a'- 相等 ->
dp[3][2] = dp[2][1] + int('a') = 101 + 97 = 198
- 相等 ->
-
i=3, j=3: 比较s1[2]='a'和s2[2]='t'- 不相等 ->
dp[3][3] = max(dp[2][3], dp[3][2]) = max(101, 198) = 198
- 不相等 ->
最终的DP表如下:
"" "e" "ea" "eat" "" 0 0 0 0 "s" 0 0 0 0 "se" 0 101 101 101 "sea" 0 101 198 198 -
-
得出答案:
- 最大公共子序列ASCII和
dp[3][3] = 198。这个公共子序列是 "ea"。 - 最小ASCII删除和 =
total_ascii - 2 * dp[m][n] = 627 - 2 * 198 = 627 - 396 = 231。
- 最大公共子序列ASCII和
这个结果与示例中的解释完全一致。通过这个动态规划过程,我们成功地找到了使两个字符串相等所需的最小删除成本。