(LeetCode-Hot100)72. 编辑距离

问题简介

72. 编辑距离 - LeetCode

题目描述

给你两个单词 word1word2,请你计算出将 word1 转换成 word2 所使用的最少操作数。

你可以对一个单词进行如下三种操作:

  • 插入一个字符
  • 删除一个字符
  • 替换一个字符

示例说明

示例 1:

复制代码
输入:word1 = "horse", word2 = "ros"
输出:3
解释:
horse -> rorse (将 'h' 替换为 'r')
rorse -> rose (删除 'r')
rose -> ros (删除 'e')

示例 2:

复制代码
输入:word1 = "intention", word2 = "execution"
输出:5
解释:
intention -> inention (删除 't')
inention -> enention (将 'i' 替换为 'e')
enention -> exention (将 'n' 替换为 'x')
exention -> exection (将 'n' 替换为 'c')
exection -> execution (插入 'u')

解题思路

📌 核心思想:动态规划(Dynamic Programming)

编辑距离是经典的 DP 问题。我们定义状态、转移方程,并逐步构建答案。


✅ 方法一:二维动态规划(标准解法)

步骤详解:

  1. 定义状态
    dp[i][j] 表示将 word1 的前 i 个字符转换为 word2 的前 j 个字符所需的最小操作数。

  2. 初始化边界条件

    • dp[0][j] = j:空字符串变 word2[:j] 需要 j 次插入。
    • dp[i][0] = iword1[:i] 变空字符串需要 i 次删除。
  3. 状态转移方程

    对于 i > 0j > 0

    • 如果 word1[i-1] == word2[j-1],则无需操作:
      dp[i][j] = dp[i-1][j-1]

    • 否则,取三种操作的最小值 + 1:

      复制代码
      dp[i][j] = min(
          dp[i-1][j] + 1,     // 删除 word1[i-1]
          dp[i][j-1] + 1,     // 插入 word2[j-1]
          dp[i-1][j-1] + 1    // 替换 word1[i-1] 为 word2[j-1]
      )
  4. 返回结果
    dp[m][n] 即为答案(m = len(word1), n = len(word2)


✅ 方法二:空间优化的一维 DP

💡 观察dp[i][j] 仅依赖于上一行和当前行的左侧值,因此可用滚动数组优化空间。

  • 使用一维数组 dp[j] 表示当前行。
  • 用变量 prev 保存 dp[i-1][j-1](左上角)。
  • 遍历更新时注意顺序。

本题以清晰性优先,以下代码实现以方法一为主,但会在 Go 中展示空间优化版本作为对比。


代码实现

java:Java 复制代码
class Solution {
    public int minDistance(String word1, String word2) {
        int m = word1.length();
        int n = word2.length();
        
        // dp[i][j]: word1前i个字符 -> word2前j个字符的最小操作数
        int[][] dp = new int[m + 1][n + 1];
        
        // 初始化边界
        for (int i = 0; i <= m; i++) {
            dp[i][0] = i;
        }
        for (int j = 0; j <= n; j++) {
            dp[0][j] = j;
        }
        
        // 填充DP表
        for (int i = 1; i <= m; i++) {
            for (int j = 1; j <= n; j++) {
                if (word1.charAt(i - 1) == word2.charAt(j - 1)) {
                    dp[i][j] = dp[i - 1][j - 1];
                } else {
                    dp[i][j] = Math.min(
                        Math.min(dp[i - 1][j], dp[i][j - 1]),
                        dp[i - 1][j - 1]
                    ) + 1;
                }
            }
        }
        
        return dp[m][n];
    }
}
go:Go 复制代码
// 方法一:二维DP(清晰版)
func minDistance(word1 string, word2 string) int {
    m, n := len(word1), len(word2)
    dp := make([][]int, m+1)
    for i := range dp {
        dp[i] = make([]int, n+1)
        dp[i][0] = i
    }
    for j := 0; j <= n; j++ {
        dp[0][j] = j
    }

    for i := 1; i <= m; i++ {
        for j := 1; j <= n; j++ {
            if word1[i-1] == word2[j-1] {
                dp[i][j] = dp[i-1][j-1]
            } else {
                dp[i][j] = min(min(dp[i-1][j], dp[i][j-1]), dp[i-1][j-1]) + 1
            }
        }
    }
    return dp[m][n]
}

// 方法二:空间优化(一维DP)
func minDistanceOptimized(word1 string, word2 string) int {
    m, n := len(word1), len(word2)
    if m < n {
        return minDistanceOptimized(word2, word1) // 确保 word1 更长,节省空间
    }
    
    dp := make([]int, n+1)
    for j := 0; j <= n; j++ {
        dp[j] = j
    }
    
    for i := 1; i <= m; i++ {
        prev := dp[0] // 保存 dp[i-1][j-1]
        dp[0] = i
        for j := 1; j <= n; j++ {
            temp := dp[j]
            if word1[i-1] == word2[j-1] {
                dp[j] = prev
            } else {
                dp[j] = min(min(dp[j], dp[j-1]), prev) + 1
            }
            prev = temp
        }
    }
    return dp[n]
}

func min(a, b int) int {
    if a < b {
        return a
    }
    return b
}

示例演示

word1 = "horse", word2 = "ros" 为例:

构建 DP 表如下(✅ 表示匹配,无需操作):

"" r o s
"" 0 1 2 3
h 1 1 2 3
o 2 2 1 2
r 3 2 2 2
s 4 3 3 2
e 5 4 4 3

最终 dp[5][3] = 3,符合预期。


答案有效性证明

数学归纳法验证 DP 正确性

  • 基础情况:空字符串到任意字符串的操作数显然为长度(全插入/删除),初始化正确。
  • 归纳假设 :假设所有 dp[i'][j'](其中 i' < ij' < j)已正确计算。
  • 归纳步骤
    • 若末尾字符相同,则最优解必不操作该字符,故 dp[i][j] = dp[i-1][j-1]
    • 若不同,则最后一次操作必为插入、删除或替换之一,取三者最小值加 1,覆盖所有可能。
  • 因此,DP 状态转移覆盖所有情况,结果最优。

复杂度分析

方法 时间复杂度 空间复杂度
二维 DP O ( m t i m e s n ) O(m \\times n) O(mtimesn) O ( m t i m e s n ) O(m \\times n) O(mtimesn)
一维 DP(优化) O ( m t i m e s n ) O(m \\times n) O(mtimesn) O ( m i n ( m , n ) ) O(\\min(m, n)) O(min(m,n))

其中 m = t e x t l e n ( w o r d 1 ) m = \\text{len}(word1) m=textlen(word1), n = t e x t l e n ( w o r d 2 ) n = \\text{len}(word2) n=textlen(word2)


问题总结

📌 关键点回顾

  • 编辑距离是字符串 DP 的经典模型,掌握其状态定义和转移逻辑至关重要。
  • 三种操作(插入、删除、替换)在 DP 中对称体现,可通过"对齐"思想理解。
  • 空间优化技巧适用于所有"只依赖前一行"的二维 DP 问题。

💡 扩展思考

  • 若允许"交换相邻字符"操作(如 Damerau-Levenshtein 距离),如何修改?
  • 在实际应用中(如拼写检查、DNA 序列比对),编辑距离是基础算法。

LeetCode Hot 100 中此题地位

作为字符串 + 动态规划的代表题,是必须掌握的核心题型之一。

github地址: https://github.com/swf2020/LeetCode-Hot100-Solutions

相关推荐
musenh1 小时前
springmvc学习
java·学习
啊阿狸不会拉杆1 小时前
《计算机视觉:模型、学习和推理》第 2 章-概率概述
人工智能·python·学习·算法·机器学习·计算机视觉·ai
石牌桥网管1 小时前
golang Context介绍
开发语言·算法·golang
Hello.Reader1 小时前
Flink State Backend 选型、配置、RocksDB 调优、ForSt 与 Changelog 一次讲透
java·网络·数据库
_OP_CHEN1 小时前
【算法提高篇】(四)线段树之多个区间操作:懒标记优先级博弈与实战突破
算法·蓝桥杯·线段树·c/c++·区间查询·acm、icpc·区间操作
俩娃妈教编程1 小时前
2025 年 09 月 三级真题(1)--数组清零
c++·算法·gesp真题
人道领域2 小时前
Maven多环境配置实战指南
java·数据库·spring
AI科技星2 小时前
时空的几何动力学:基于光速螺旋运动公设的速度上限定理求导与全维度验证
人工智能·线性代数·算法·机器学习·平面
㓗冽2 小时前
进制转换(字符串)-基础题82th + 表达式求值(字符串)-基础题83th + 删除字符(字符串)-基础题84th
算法