72.Edit Distance

25 年 8 月 22 日 星期五
682 字
4 分钟

72. 编辑距离

Screenshot 2026-03-08 at 7.20.19 pm
image-20250822175154451
js
/**
 * 计算两个字符串之间的最小编辑距离(莱文斯坦距离)
 * 编辑距离定义:将字符串 s 转换成字符串 t 所需的最少操作数
 * 允许的操作:插入一个字符、删除一个字符、替换一个字符
 * @param {string} s - 源字符串
 * @param {string} t - 目标字符串
 * @returns {number} 最小编辑距离
 */
function minDistance(s, t) {
  // 获取两个字符串的长度
  const n = s.length
  const m = t.length

  // 创建记忆化数组 memo,用于存储已经计算过的子问题结果
  // memo[i][j] 表示 s[0...i] 和 t[0...j] 的最小编辑距离
  // 初始值设为 -1,表示该子问题尚未计算
  const memo = new Array(n)
  for (let i = 0; i < n; i++) {
    memo[i] = new Array(m).fill(-1)
  }

  /**
   * 深度优先搜索(DFS)+ 记忆化递归函数
   * @param {number} i - 源字符串 s 的当前索引
   * @param {number} j - 目标字符串 t 的当前索引
   * @returns {number} s[0...i] 和 t[0...j] 的最小编辑距离
   */
  function dfs(i, j) {
    // 情况1:源字符串 s 已经遍历完(i < 0)
    // 此时需要把 t 中剩余的 j+1 个字符全部插入到 s 中,操作数为 j+1
    if (i < 0) {
      return j + 1
    }
    // 情况2:目标字符串 t 已经遍历完(j < 0)
    // 此时需要把 s 中剩余的 i+1 个字符全部删除,操作数为 i+1
    if (j < 0) {
      return i + 1
    }

    // 如果当前子问题已经计算过,直接返回记忆化的结果,避免重复计算
    if (memo[i][j] !== -1) {
      return memo[i][j]
    }

    let res
    // 情况3:当前字符相等,无需任何操作,直接递归处理前一个字符
    if (s[i] === t[j]) {
      res = dfs(i - 1, j - 1)
    } else {
      // 情况4:当前字符不相等,需要选择三种操作中代价最小的一种
      // 1. dfs(i-1, j) + 1:删除 s[i],操作数+1
      // 2. dfs(i, j-1) + 1:插入 t[j] 到 s[i] 后面,操作数+1
      // 3. dfs(i-1, j-1) + 1:替换 s[i] 为 t[j],操作数+1
      res = Math.min(dfs(i - 1, j), dfs(i, j - 1), dfs(i - 1, j - 1)) + 1
    }

    // 将当前子问题的结果存入记忆化数组,供后续重复子问题使用
    memo[i][j] = res
    return res
  }

  // 从两个字符串的最后一个字符开始递归计算
  return dfs(n - 1, m - 1)
}

在这段代码里,dfs(i, j) 是一个有明确语义的递归函数,它的定义是:

计算「字符串 s 的前 i+1 个字符(s [0]~s [i])」转换为「字符串 t 的前 j+1 个字符(t [0]~t [j])」所需的最小操作数(也就是这两个子串的最小编辑距离)。

文章标题:72.Edit Distance

文章作者:Sirui Chen

文章链接:https://blog.siruichen.me/posts/72edit_distance[复制]

最后修改时间:


商业转载请联系站长获得授权,非商业转载请注明本文出处及文章链接,您可以自由地在任何媒体以任何形式复制和分发作品,也可以修改和创作,但是分发衍生作品时必须采用相同的许可协议。
本文采用CC BY-NC-SA 4.0进行许可。