面试被这道题卡住?5分钟带你搞懂「最小ASCII删除和」

面试被这道题卡住?5分钟带你搞懂「最小ASCII删除和」

“给定两个字符串,删掉一些字符让它们相等,但删掉的每个字符都要’收费’——按 ASCII 值收费。怎么删最省钱?”

这不是什么奇怪的计价规则,而是 LeetCode 第 712 题——两个字符串的最小 ASCII 删除和

它和大名鼎鼎的编辑距离是亲兄弟,但面试命中率却不相上下。搞懂了这道题,你对二维 DP 的理解就真正毕业了。


先举个栗子:到底在问什么?

输入:s1 = "sea", s2 = "eat"
输出:231

解释:

  • "sea" 中删掉 's'(ASCII 值 115),变成 "ea"
  • "eat" 中删掉 't'(ASCII 值 116),变成 "ea"
  • 两个字符串相等了!花费:115 + 116 = 231

注意: 你不能插入或替换,只能删除。而且删哪个字符是有代价的——按它的 ASCII 值收费。


核心直觉:这道题和编辑距离有什么区别?

如果你做过编辑距离(LeetCode 72),你可能会有既视感:

编辑距离最小 ASCII 删除和
允许的操作增、删、改只能删
每次操作的代价都是 1被删字符的 ASCII 值
DP 维度二维二维

所以本质上,这是编辑距离的简化版 + 加权版

  • 简化:不用考虑"插入"和"替换"
  • 加权:删除代价从固定的 1 变成了字符的 ASCII 值

五步法推导:从零到完整代码

第一步:定义状态

dp[i][j] = 使 s1 的前 i 个字符 和 s2 的前 j 个字符 相等,所需要删除的字符的 ASCII 值的最小和

进一步理解:dp[i][j] = 从 s1 的前 i 个字符中删一些,从 s2 的前 j 个字符中删一些,使得剩下的字符串相同**,所需删除字符的 ASCII 值的最小和**

这里的 ij长度(前几个字符),不是下标。


第二步:状态转移方程

假设现在处理 dp[i][j],两字符串的最后一个字符分别是 s1[i-1]s2[j-1]

✅ 情况一:两个字符相等
s1[i-1] == s2[j-1]

太好了!这个字符不用删,问题缩小一格:

dp[i][j] = dp[i-1][j-1]


✅ 情况二:两个字符不相等

只能删除,有两种选择:

选择做了什么花费
删除 s1[i-1]s1 少了一个字符,s2 目标不变dp[i-1][j] + s1[i-1]
删除 s2[j-1]s2 少了一个字符,s1 不变dp[i][j-1] + s2[j-1]

取最小值:

dp[i][j] = min(dp[i-1][j] + s1[i-1], dp[i][j-1] + s2[j-1])


第三步:初始化边界

画个表格就懂了(以 s1="sea", s2="eat" 为例):

“”eat
“”0???
s????
e????
a????
  • dp[0][0] = 0:空串转空串,不需要删
  • dp[i][0]:s1 前 i 个字符变成空串,只能全部删掉

    dp[i][0] = dp[i-1][0] + s1[i-1]

  • dp[0][j]:空串变成…(即 s2 前 j 个字符全部删掉)

    dp[0][j] = dp[0][j-1] + s2[j-1]


第四步:遍历顺序

dp[i][j] 依赖三个方向:

↖  dp[i-1][j-1]   ↑  dp[i-1][j]
←  dp[i][j-1]      ?  dp[i][j]

必须从左到右、从上到下遍历。

for (int i = 1; i <= m; i++) {
    for (int j = 1; j <= n; j++) {
        // 计算 dp[i][j]
    }
}

第五步:验证填表

手动填一遍 s1="sea", s2="eat" 的表格:

“”e(101)a(97)t(116)
“”0101198314
s(115)115216313429
e(101)216115212328
a(97)313212115231

几个关键推导:

  • dp[2][1]s1[1]='e', s2[0]='e' 相等dp[1][0] = 115
  • dp[3][2]s1[2]='a', s2[1]='a' 相等dp[2][1] = 115
  • dp[3][3]s1[2]='a', s2[2]='t' 不相等
    • adp[2][3] + 97 = 328 + 97 = 425
    • tdp[3][2] + 116 = 115 + 116 = 231
    • 取最小:231

答案正确!


完整代码(Java)

class Solution {
    public int minimumDeleteSum(String s1, String s2) {
        int m = s1.length(), n = s2.length();
        int[][] dp = new int[m + 1][n + 1];

        // 初始化第一列:s1 前 i 个字符全部删除
        for (int i = 1; i <= m; i++) {
            dp[i][0] = dp[i - 1][0] + s1.charAt(i - 1);
        }

        // 初始化第一行:s2 前 j 个字符全部删除
        for (int j = 1; j <= n; j++) {
            dp[0][j] = dp[0][j - 1] + s2.charAt(j - 1);
        }

        // 填表
        for (int i = 1; i <= m; i++) {
            for (int j = 1; j <= n; j++) {
                if (s1.charAt(i - 1) == s2.charAt(j - 1)) {
                    // 字符相等,不用删
                    dp[i][j] = dp[i - 1][j - 1];
                } else {
                    // 二选一:删 s1 的字符,或删 s2 的字符
                    dp[i][j] = Math.min(
                        dp[i - 1][j] + s1.charAt(i - 1),   // 删除 s1[i-1]
                        dp[i][j - 1] + s2.charAt(j - 1)    // 删除 s2[j-1]
                    );
                }
            }
        }

        return dp[m][n];
    }
}

时间复杂度:O(m * n)
空间复杂度:O(m * n)(可优化到 O(min(m,n)))


一句话总结

这道题就是编辑距离的"删减版"——把三种操作缩成一种删除操作,把固定代价 1 换成字符 ASCII 值。

只要记住这个公式:

相等:dp[i][j] = dp[i-1][j-1]
不等:dp[i][j] = min(删s1, 删s2)

二维字符串 DP 的核心套路你就掌握了。下一道最长公共子序列,也是同样的配方。


觉得有用?收藏起来,面试前翻一翻,DP 稳如老狗。


欢迎在评论区交流你的 DP 学习心得,或者留下你想了解的算法题,下期安排!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值