问题:给你两个单词 word1
和 word2
, 请返回将 word1
转换成 word2
所使用的最少操作数 。
你可以对一个单词进行如下三种操作:插入一个字符,删除一个字符,替换一个字符。
算法1:递归搜索 + 保存计算结果 = 记忆化搜索
创建 memo 数组,并赋初始值为 -1,表示还没有被计算过。
进入 dfs 函数。引用 memo 数组,如果这个字母已经被计算过了就 return memo 的值。有四种情况,两个字母相等,这个位置就不用操作,向前继续递归;如果不相等,可以插入字母、删除字母、替换字母,取操作数最小的一个。
例如在 word1 中插入字母,那么这个位置的问题就解决了,这个时候要让 word2 的指针前移,继续递归。如果在 word1 中删除字母,那么就要将 word1 的指针前移。如果替换字母,就让 word1 和 word2 的指针同时前移。
dfs 中前两行代码表示的意思是,如果在递归过程中出现了 i 或者 j 小于 0 的情况,即 word1 或者 word2 已经遍历完了,那么另一个没有被遍历完的 word 剩余的字母个数就是 j + 1 或者 i + 1 ,这个时候直接 return j + 1 或者 i + 1 即可。
代码:
class Solution {
public:
int minDistance(string word1, string word2) {
int n = word1.length(), m = word2.length();
vector<vector<int>> memo(n, vector<int>(m, -1)); // -1 表示还没有计算过
auto dfs = [&](auto&& dfs, int i, int j) -> int {
if (i < 0) return j + 1;
if (j < 0) return i + 1;
int& res = memo[i][j]; // 注意这里是引用
if (res != -1) return res; // 之前算过了
if (word1[i] == word2[j]) return res = dfs(dfs, i - 1, j - 1);
return res = min(min(dfs(dfs, i - 1, j), dfs(dfs, i, j - 1)), dfs(dfs, i - 1, j - 1)) + 1;
};
return dfs(dfs, n - 1, m - 1); // 递归入口
}
};
算法2:1:1 翻译成递推
创建二维数组 dp ,并赋初始值 0 。初始化第 0 行第 0 列,先赋最大值,即当前位置可能出现的最大值。
开始循环递推,如果 word1 当前字母与 word2 当前字母相等,则 这个位置的操作数 dp 就等于 dp 前一个格子的值。
代码:
class Solution {
public:
int minDistance(string word1, string word2) {
int n = word1.length(), m = word2.length();
vector<vector<int>> dp(n + 1,vector<int>(m + 1));
for(int j = 0;j <= m;j++) dp[0][j] = j; // 初始化第0行
for(int i = 0; i < n; i++){
dp[i + 1][0] = i + 1; // 初始化第0列
for(int j = 0;j < m;j++)
dp[i + 1][j + 1] = word1[i] == word2[j] ? dp[i][j] : min(min(dp[i][j],dp[i + 1][j]),dp[i][j + 1]) + 1;
}
return dp[n][m];
}
};
算法3:空间优化:两个数组(滚动数组)
通过 算法2 可知,行列表我们只会用到 2 行,每次递推只会取它的上一个格子的值,所以可以利用循环数组,来有效降低空间复杂度。
代码:
class Solution {
public:
int minDistance(string word1, string word2) {
int n = word1.length(),m = word2.length();
vector<vector<int>> dp(2,vector<int>(m + 1));
for(int j = 0;j <= m;j++) dp[0][j] = j;
for(int i = 0;i < n;i++){
dp[(i + 1) % 2][0] = i + 1;
for(int j = 0;j < m;j++)
dp[(i + 1) % 2][j + 1] = word1[i] == word2[j] ? dp[i % 2][j] : min(min(dp[i % 2][j],dp[(i + 1) % 2][j]),dp[i % 2][j + 1]) + 1;
}
return dp[n % 2][m];
}
};
算法3:空间优化:一个数组
创建一维数组 dp 并赋初始值。
dp [ 0 ] 相当于原来的 dp [ 0 ] [ ] ,通过内层循环每循环一次就通过 dp [ 0 ] 自增来实现 + 1 。
pre 相当于 dp [ i + 1 ] [ j ] ;
没更新的 dp[ j + 1] 相当于 dp [ i ] [ j + 1] ;
dp[ j ] 相当于 dp [ i ] [ j ];
更新后的 dp[ j + 1] 相当于 dp [ i + 1] [ j + 1] 。
代码:
class Solution {
public:
int minDistance(string word1, string word2) {
int m = word2.length();
vector<int> dp(m + 1);
for(int j = 0;j <= m;j++) dp[j] = j; // iota(dp.begin(), dp.end(), 0);
for(char x : word1){
int pre = dp[0];
dp[0]++; // 对应二维数组方法时行数向下移,即 dp[i+1][0] = i+1
for(int j = 0;j < m;j++){
int temp = dp[j + 1];
dp[j + 1] = x == word2[j] ? pre : min(min(dp[j + 1],dp[j]),pre) + 1;
pre = temp;
}
}
return dp[m];
}
};