前端算法第一七七期-两个字符串的最小ASCII删除和

89 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第10天,点击查看活动详情

给定两个字符串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",我们会得到 433417 的结果,比答案更大。

动态规划

我们用 dp[i][j]dp[i][j] 表示字符串 s1[i:]s1[i:]s2[j:]s2[j:]s1[i:]s1[i:] 表示字符串 s1s1 从第 ii 位到末尾的子串,s2[j:]s2[j:] 表示字符串 s2s2 从第 jj 位到末尾的子串,字符串下标从 0 开始)达到相等所需删除的字符的 ASCII 值的最小和,最终的答案为 dp[0][0]dp[0][0]

s1[i:]s2[j:] 中的某一个字符串为空时,dp[i][j] 的值即为另一个非空字符串的所有字符的 ASCII 值之和。例如当 s2[j:] 为空时,此时有 j = s2.length(),状态转移方程为

dp[i][j] = s1.asciiSumFromPos(i)

也可以写成递推的形式,即

dp[i][j] = dp[i + 1][j] + s1.asciiAtPos(i)

对于其余的情况,即两个字符串都非空时,如果有 s1[i] == s2[j],那么当前位置的两个字符相同,它们不需要被删除,状态转移方程为

dp[i][j] = dp[i + 1][j + 1]

如果 s1[i] != s2[j],那么我们至少要删除 s1[i]s2[j] 两个字符中的一个,因此状态转移方程为

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

/**
 * @param {string} s1
 * @param {string} s2
 * @return {number}
 */
var minimumDeleteSum = function(s1, s2) {
  let m = s1.length,
    n = s2.length;
  // 备忘录值为 -1 代表未曾计算
  let memo = new Array(m).fill(-1).map(() => new Array(n).fill(-1));
  const dp = (s1, i, s2, j) => {
    /**
     * 定义:将 s1[i..] 和 s2[j..] 删除成相同字符串,
     * 最小的 ASCII 码之和为 dp(s1, i, s2, j)。
     */
    let res = 0;
    // base case
    if (i == s1.length) {
      // 如果 s1 到头了,那么 s2 剩下的都得删除
      for (; j < s2.length; j++) {
        res += s2.charCodeAt(j);
      }
      return res;
    }
    if (j == s2.length) {
      // 如果 s2 到头了,那么 s1 剩下的都得删除
      for (; i < s1.length; i++) {
        res += s1.charCodeAt(i);
      }
      return res;
    }
    if (memo[i][j] != -1) return memo[i][j];
    if (s1.charCodeAt(i) == s2.charCodeAt(j)) {
      // s1[i] 和 s2[j] 都是在 lcs 中的,不用删除
      memo[i][j] = dp(s1, i + 1, s2, j + 1);
    } else {
      // s1[i] 和 s2[j] 至少有一个不在 lcs 中,删一个
      memo[i][j] = Math.min(
        s1.charCodeAt(i) + dp(s1, i + 1, s2, j),
        s2.charCodeAt(j) + dp(s1, i, s2, j + 1)
      );
    }
    return memo[i][j];
  };

  return dp(s1, 0, s2, 0);
};