📝 题目描述
题目链接:编辑距离
给你两个单词 word1 和 word2, 请返回将 word1 转换成 word2 所使用的最少操作数。
你可以对一个单词进行如下三种操作:
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 示例 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')
|
提示:
0 <= word1.length, word2.length <= 500
word1 和 word2 由小写英文字母组成
💡 解题思路
方法一:动态规划
这个题目可以参照“最长公共子序列”的 dp 思路,即创建二维数组 dp[i][j],代表将 word1 的前 i 个字符,转换成 word2 的前 j 个字符,所需要的最少操作数。
上述表示中,需要注意 dp[i][j] 中的 i 和 j 代表的是前 i 和 j 个字符,因此当 i 或 j 为 0 时,则代表前 0 个字符,也就是空串。
首先我们需要初始化,dp[i][0]=i,不难想象,把一个长度为 i 的单词变成空字符串,唯一的办法就是删除 i 次。dp[0][j]=j,把一个空字符串变成长度为 j 的单词,唯一的办法就是插入 j 次。
接下来开始找出状态转移方程,考虑两个非空前缀,把它们写成:
A+a⟶B+b
其中,a、b 是末尾字符,A、B 是去掉末尾字符后的前缀。
假设 a != b,我们有三种方式完成转换:
| 选择 |
方法 |
操作数 |
删除a |
删掉源前缀末尾的a,再把 A 变成 B+b |
1+dp[i−1][j] |
插入b |
先把A+a 变成 B,再在末尾插入 b |
dp[i][j−1]+1 |
把a 替换成 b |
先把A 变成 B,再把末尾的 a 替换成 b |
dp[i−1][j−1]+1 |
因此,
dp[i][j]=min(dp[i−1][j]+1,dp[i][j−1]+1,dp[i−1][j−1]+1);
同时,如果 a = b,那么问题更加简单,只需要将把 A 变成 B 即可;
dp[i][j]=dp[i−1][j−1]+1;
综上我们得到了算法的全部流程。
🔧 代码实现
1、动态规划
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| class Solution { public: int minDistance(string word1, string word2) { int m = word1.size(), n = word2.size(); vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0)); for (int i = 0; i <= n; i++) { dp[0][i] = i; } for (int i = 0; i <= m; i++) { dp[i][0] = i; } for (int i = 1; i <= m; i++) { for (int 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({dp[i - 1][j - 1], dp[i][j - 1], dp[i - 1][j]}) + 1; } } } return dp[m][n]; } };
|
📊 复杂度分析
1、动态规划
- 时间复杂度:O(mn),其中 m 为 word1 的长度,n 为 word2 的长度。
- 空间复杂度:O(mn),我们需要大小为 m×n 的数组来记录状态值。
🎯 总结
- 核心思想:仿照“最长公共子序列”的 dp 思路解题。