动态规划之最短编辑距离
本文记录一下Leetcode动态规划: 最短编辑距离问题的解法。
1. 题目描述
给你两个单词 word1 和 word2, 请返回将 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')
2. 解题思路
深度理解编辑的含义:
-
如果$word1[i] == word2[j]$, 那么我们接下来只需要比较$word1[0 .. i-1]$和$word2[0 .. j-1]$
-
否则,有三个选项:
-
执行插入操作:那么接下来只需要比较$word1[0..i]$和$word2[0..j-1]$
-
执行删除操作:那么接下来只需要比较$word1[0..i-1]$和$word2[0..j]$
-
执行替换操作: 那么接下来只需要比较$word1[0..i-1]$和$word2[0..j-1]$
ps: 选择上述三个选项中最小的那个加1
-
模式识别:
- 一旦涉及子问题,可以用自顶向下的递归,和自底向上的动态规划
3. 算法实现
递归解法
class Solution {
public:
int minDistance(string word1, string word2) {
if(word1.length() == 0 || word2.length() == 0){
return max(word1.length(), word2.length());
}
if(word1.back() == word2.back()){
return minDistance(word1.substr(0, word1.length()-1), word2.substr(0, word2.length() -1));
}
//计算执行插入操作
int a = 1 + minDistance(word1, word2.substr(0, word2.length()-1));
//计算执行删除操作
int b = 1 + minDistance(word1.substr(0, word1.length() -1), word2);
//计算执行替换操作
int c = 1 + minDistance(word1.substr(0, word1.length() -1), word2.substr(0, word2.length() -1));
return min(min(a, b), c);
}
};
对于上述递归算法的实现,在运行时会超时,原因是存在大量的重复计算:
//计算执行插入操作
int a = 1 + minDistance(word1, word2.substr(0, word2.length()-1));
//计算执行删除操作
int b = 1 + minDistance(word1.substr(0, word1.length() -1), word2);
//计算执行替换操作
int c = 1 + minDistance(word1.substr(0, word1.length() -1), word2.substr(0, word2.length() -1));
这里我们有两种优化方式:
-
Memorization
-
动态规划
动态规划解法
我们从上面的解题思路中抽象出状态转移方程:
-
如果$word1[i]==word2[j]$,那么$op[i][j]=op[i-1][j-1]$
-
否则, $op[i][j]=1+min(op[i][j-1], op[i-1][j], op[i-1][j-1])$
ps: 这里的op[i][j]的含义为将word1[0..i]变成word2[0..j]所需要的最小操作次数
- 动态规划实现方式1
class Solution {
public:
int minDistance(string word1, string word2) {
int length1 = word1.size();
int length2 = word2.size();
vector<vector<int>> op;
//定义出口: 即word1为空或者word2为空的情况
for(int i = 0; i < length1 + 1, i++){
vector<int> row;
for(int j = 0; j < length2 + 1; j++){
if (i == 0){
row.push_back(j);
}else if(j == 0){
row.push_back(i);
}else{
row.push_back(0);
}
}
op.push_back(row);
}
//根据转移方程实现动态规划
for(int i = 0; i < length1; i++){
for(int j = 0; j <length2; j++){
if(word1[i] == word2[j]){
op[i+1][j+1] = op[i][j];
}else{
op[i+1][j+1] = 1 + min(min(op[i+1][j], op[i][j+1]), op[i][j]);
}
}
}
return op[length1][length2];
}
};
- 动态规划实现方式2
class Solution {
public:
int minDistance(string word1, string word2) {
int n = word1.length();
int m = word2.length();
// 有一个字符串为空串
if (n * m == 0) return n + m;
// DP 数组
vector<vector<int>> D(n + 1, vector<int>(m + 1));
// 边界状态初始化
for (int i = 0; i < n + 1; i++) {
D[i][0] = i;
}
for (int j = 0; j < m + 1; j++) {
D[0][j] = j;
}
// 计算所有 DP 值
for (int i = 1; i < n + 1; i++) {
for (int j = 1; j < m + 1; j++) {
int left = D[i - 1][j] + 1; //执行删除操作
int down = D[i][j - 1] + 1; //执行插入操作
int left_down = D[i - 1][j - 1];
if (word1[i - 1] != word2[j - 1]) left_down += 1;
D[i][j] = min(left, min(down, left_down));
}
}
return D[n][m];
}
};
[参看]: