https://leetcode-cn.com/problems/edit-distance/
- 思路
- 如果word1[i]和word2[j]匹配
- 则i++,j++继续比较下一个字符,无需替换、插入和删除操作
- 如果word1[i]和word2[j]不匹配
- 1、删除word1[i]
- 然后递归考察word1[i+1]和word2[j]
- 2、删除word2[j]
- 然后递归考察word1[i]和word2[j+1]
- 3、在word1[i]前面添加一个和word2[j]相同的字符
- 然后递归考察word1[i]和word2[j+1]
- 4、在word2[j]前面添加一个和word1[i]相同的字符
- 然后递归考察word1[i+1]和word2[j]
- 5、将word1[i]替换成word2[j]或者将word2[j]替换成word1[i]
- 然后递归考察word1[i+1]和word2[j+1]
- 1、删除word1[i]
- 如果word1[i]和word2[j]匹配
- 结果:超时
- 有很多重复子问题,也因此可以用更加高效的动态规划解决
/**
* @param {string} word1
* @param {string} word2
* @return {number}
*/
var minDistance = function(word1, word2) {
var n = word1.length;
var m = word2.length;
var minDist = Number.MAX_SAFE_INTEGER;
function helper(i,j,edist){
// terminator
if(i == n || j == m){
// 没有剩余字符的单词要加上有剩余字符单词的剩余字符个数
if(i < n){
edist+=(n-i);
}
if(j < m){
edist+=(m-j);
}
minDist = Math.min(edist,minDist);
return;
}
// drill down
// 如果两个子字符匹配即相同
if(word1[i] == word2[j]){
helper(i+1,j+1,edist);
}else{
// 删除a[i] 或 b[j]前添加一个字符
helper(i+1,j,edist+1);
// 删除b[j] 或 a[i]前添加一个字符
helper(i,j+1,edist+1);
// 将a[i]和b[j]替换为相同字符
helper(i+1,j+1,edist+1);
}
}
helper(0,0,0);
return minDist;
};
- 图解
- 在解法一的递归树中,(i,j)两个变量重复节点很多
- 因此对于(i,j)相同的节点,只需要保留最小的edist即可
- 由解法一的(i,j,edist)状态变成了(i,j,min_edist)
- 状态(i,j)可以从以下三个状态中的任意一个转移过来
- (i-1,j,min_edist)
- (i,j-1,min_edist)
- (i-1,j-1,min_edist)
- 状态转移方程
- 当word1[i] != word2[j]时
- min_edist(i,j) = Min(min_edist(i-1,j)+1,min_edist(i,j-1)+1,min_edist(i-1,j-1)+1)
- 当word1[i] == word2[j]时
- min_edist(i,j) = Min(min_edist(i-1,j)+1,min_edist(i,j-1)+1,min_edist(i-1,j-1))
- 当word1[i] != word2[j]时
- 状态(i,j)可以从以下三个状态中的任意一个转移过来
/**
* @param {string} word1
* @param {string} word2
* @return {number}
*/
var minDistance = function(word1, word2) {
if(word1 == word2){
return 0;
}
// 行
var n = word1.length;
// 列
var m = word2.length;
if(!n || !m){
return n || m;
}
var minDist = Array.from(new Array(n),() => new Array(m));
// 初始化第1列第n行的数据
for(var i = 0;i < n;++i){
if(word1[i] == word2[0]){
minDist[i][0] = i;
}else{
if(i == 0){
minDist[i][0] = 1;
}else{
minDist[i][0] = minDist[i-1][0] + 1;
}
}
}
// 初始化第1行第n列的数据
for(var j = 0;j < m;++j){
if(word1[0] == word2[j]){
minDist[0][j] = j;
}else{
if(j == 0){
minDist[0][j] = 1;
}else{
minDist[0][j] = minDist[0][j-1] + 1;
}
}
}
for(var i = 1;i < n;++i){
for(var j = 1;j < m;++j){
if(word1[i] == word2[j]){
minDist[i][j] = Math.min(minDist[i-1][j]+1,minDist[i][j-1]+1,minDist[i-1][j-1]);
}else{
minDist[i][j] = Math.min(minDist[i-1][j]+1,minDist[i][j-1]+1,minDist[i-1][j-1]+1);
}
}
}
return minDist[n-1][m-1];
};
- 状态定义
- dp[i][j]:代表word1到i位置要转换成word2到j位置所需要到最少步数
- dp[i-1][j-1]:替换
- dp[i-1][j]:删除或插入(添加)
- dp[i][j-1]:删除或插入(添加)
- 状态转移方程
- 当word1[i] == word2[j]时
- dp[i][j] = Min(dp[i][j-1],dp[i-1][j])+1 或者
- dp[i][j] = dp[i-1][j-1]
- 当word1[i] != word2[j]时
- dp[i][j] = Min(dp[i][j-1],dp[i-1][j],dp[i-1][j-1])+1
- 当word1[i] == word2[j]时
- 解题技巧
- 第一种情况时,dp[i][j]也有两种情况
- 可以把dp[i][j]的第一种情况合并到word1[i] != word2[j]中去
- 第二种情况仅在word1[i-1] == word2[j-1]时出现
- 因此递推公式可以精简为
- 当word1[i-1] == word[j-1]时
- dp[i][j] = dp[i-1][j-1]
- 否则
- dp[i][j] = Min(dp[i-1][j],dp[i][j-1],dp[i-1][j-1]) + 1
- 当word1[i-1] == word[j-1]时
- 第一种情况时,dp[i][j]也有两种情况
/**
* @param {string} word1
* @param {string} word2
* @return {number}
*/
var minDistance = function(word1, word2) {
if(word1 == word2){
return 0;
}
// 行
var n = word1.length;
// 列
var m = word2.length;
if(!n || !m){
return n || m;
}
var dp = Array.from(new Array(n+1),() => new Array(m+1).fill(0));
for(var i = 1;i <= n;++i){
dp[i][0] = dp[i-1][0] + 1;
}
for(var j = 1;j <= m;++j){
dp[0][j] = dp[0][j-1] + 1;
}
for(var i = 1;i <= n;++i){
for(var j = 1;j <= m;++j){
if(word1[i-1] == word2[j-1]){
dp[i][j] = dp[i-1][j-1];
}else{
dp[i][j] = Math.min(dp[i-1][j],dp[i][j-1],dp[i-1][j-1])+1;
}
}
}
return dp[n][m];
};
- ''和非空单词的编辑距离是遍历时非空单词的索引值+1
/**
* @param {string} word1
* @param {string} word2
* @return {number}
*/
var minDistance = function(word1, word2) {
let n = word1.length;
let m = word2.length;
let dp = [];
for(let i = 0;i <= n;i++){
dp.push([])
for(let j = 0;j <= m;j++){
if(i*j){
dp[i][j] = word1[i-1] == word2[j-1]? dp[i-1][j-1]: (Math.min(dp[i-1][j],dp[i][j-1],dp[i-1][j-1]) + 1);
}else{
dp[i][j] = i + j;
}
}
}
return dp[n][m];
};