问题描述
给定两个单词 word1 和 word2,计算将 word1 转换成 word2 所使用的最少操作数。
你可以对一个单词进行如下三种操作:
- 插入一个字符
- 删除一个字符
- 替换一个字符
示例 1:
1 | 输入:word1 = "horse", word2 = "ros" |
示例 2:
1 | 输入:word1 = "intention", word2 = "execution" |
约束条件:
0 <= word1.length, word2.length <= 500word1和word2由小写英文字母组成
解题思路
这道题是经典的动态规划问题。我们可以定义 dp[i][j] 表示将 word1 的前 i 个字符转换成 word2 的前 j 个字符所需要的最少操作数。
状态定义
dp[i][j]:word1 的子串 word1[0...i-1] (长度为 i) 转换成 word2 的子串 word2[0...j-1] (长度为 j) 所需的最小操作数。
初始化
dp[0][0] = 0:空字符串转换为空字符串,操作数为 0。dp[i][0] = i(当j=0时):word1的前i个字符转换为空字符串,需要i次删除操作。
例如,将 “abc” 转换为空字符串,需要删除 ‘a’, ‘b’, ‘c’,共 3 次操作。dp[0][j] = j(当i=0时):空字符串转换成word2的前j个字符,需要j次插入操作。
例如,将空字符串转换成 “xyz”,需要插入 ‘x’, ‘y’, ‘z’,共 3 次操作。
状态转移方程
考虑 dp[i][j] 的计算,即如何将 word1[0...i-1] 转换成 word2[0...j-1]。我们可以从以下三种情况进行转移:
-
替换操作 (Modify):
如果word1[i-1] == word2[j-1],说明word1的第i个字符与word2的第j个字符相同。这种情况下,我们不需要对这两个字符进行任何操作,问题就转化为将word1[0...i-2]转换成word2[0...j-2]的最小操作数。
所以,dp[i][j] = dp[i-1][j-1]。如果
word1[i-1] != word2[j-1],说明word1的第i个字符与word2的第j个字符不同。我们可以将word1[i-1]替换为word2[j-1],这样这两个字符就匹配了。此时,操作数加 1,问题转化为将word1[0...i-2]转换成word2[0...j-2]的最小操作数。
所以,dp[i][j] = dp[i-1][j-1] + 1。 -
删除操作 (Delete):
我们可以删除word1[i-1]这个字符,然后问题就转化为将word1[0...i-2](长度为i-1) 转换成word2[0...j-1](长度为j) 的最小操作数。
所以,dp[i][j] = dp[i-1][j] + 1。 -
插入操作 (Insert):
我们可以在word1[0...i-1]的末尾插入一个字符word2[j-1],使得word1的新子串与word2[0...j-1]的末尾字符匹配。然后问题就转化为将word1[0...i-1](长度为i) 转换成word2[0...j-2](长度为j-1) 的最小操作数。
所以,dp[i][j] = dp[i][j-1] + 1。
综合以上三种情况,当 word1[i-1] != word2[j-1] 时,dp[i][j] 应该取这三种操作中操作数最小的一个:
dp[i][j] = min(dp[i-1][j-1] + 1, dp[i-1][j] + 1, dp[i][j-1] + 1)
我们可以将替换操作的两种情况合并。
如果 word1[i-1] == word2[j-1],则 cost = 0,否则 cost = 1。
那么,dp[i][j] 的状态转移方程可以统一为:
dp[i][j] = min(dp[i-1][j-1] + cost, dp[i-1][j] + 1, dp[i][j-1] + 1)
更简洁的写法是:
- 如果
word1[i-1] == word2[j-1]:
dp[i][j] = dp[i-1][j-1](此时不需要操作,等价于从dp[i-1][j-1]转移过来,操作数为 0) - 如果
word1[i-1] != word2[j-1]:
dp[i][j] = min(dp[i-1][j-1], dp[i-1][j], dp[i][j-1]) + 1
这里dp[i-1][j-1]对应替换操作,dp[i-1][j]对应删除word1[i-1],dp[i][j-1]对应在word1末尾插入word2[j-1]。
最终结果是 dp[n][m],其中 n 是 word1 的长度,m 是 word2 的长度。
代码实现
1 | // filepath: /Users/adrianwang/.leetcode/72.编辑距离.go |
复杂度分析
- 时间复杂度: O(nm),其中 n 是
word1的长度,m 是word2的长度。因为我们需要填充一个 nm 大小的 DP 表。 - 空间复杂度: O(n*m),用于存储 DP 表。
关键收获
- 动态规划状态定义:清晰地定义
dp[i][j]的含义是解决问题的关键。 - 边界条件处理:正确初始化
dp表的第一行和第一列。 - 状态转移逻辑:理解三种基本操作(插入、删除、替换)如何影响状态转移。
- 字符索引与 DP 索引:注意字符串索引
word1[i-1]对应的是dp表中的第i个元素。
这个题目是字符串动态规划中的一个基础且重要的问题,掌握其解法对于理解更复杂的字符串 DP 问题非常有帮助。