题目来源

  • leetcode

题目描述

class Solution {public:int minDistance(string word1, string word2) {}
};

题目解析

什么叫做编辑距离

编辑距离是用来量化两个字符串的相似度的

所谓编辑距离就是指,将一个字符串转换成另一个字符串,需要的最少编辑操作次数(比如增加一个字符、删除一个字符、替换一个字符)

  • 编辑距离越大,说明两个字符串的相似程度越小;
  • 编辑距离越小,说明两个字符串的相似程度越大。

对于两个完全相同的字符串来说,编辑距离就是0.

根据所包含的编辑,编辑距离有多种不同的计算方式,比较著名的有

  • 莱文斯坦距离

    • 允许增加、删除、替换字符这三个编辑操作
    • 莱温斯坦距离的大小,表示两个字符串差异的大小
  • 最长公共子串长度
    • 只允许增加、删除字符这两个编辑操作。
    • 最长公共子串的大小,表示两个字符串相似程度的大小。
  • 汉明距离
    • 只允许替换操作,因此只适用于两个相等长度的字符串。

比如,两个字符串 mitcmu 和mtacnu 的莱文斯坦距离是 3,最长公共子串长度是 4。

本题要求计算莱文斯坦距离

  • 整个求解过程,涉及多个决策阶段:

    • 我们需要依次考察一个字符串中的每个字符,跟另一个字符串中的字符是否匹配
    • 匹配的话如何处理,不匹配的话又如何处理。
  • 所以,这个问题符合多阶段决策最优解模型

回溯

回溯是一个递归处理的过程:

  • 如果word1[i]word2[j]匹配,我们递归考察word1[i+1]word2[j+1]
  • 如果word1[i]word2[j]不匹配,那我们有多种处理方式可选:
    • 可以删除word1[i],然后递归考察word1[i+1]word2[j]
    • 可以删除word2[j],然后递归考察word1[i]word2[j+1]
    • 可以在word1[i]前面添加一个跟word2[j]相同的字符,然后递归考察word1[i]word2[j+1]
    • 可以在word2[j]前面添加一个跟word1[i]相同的字符,然后递归考察word1[i+1]word2[j]
    • 可以将word1[i]替换word2[j],或者将word2[j]替换word1[i],然后递归考察 word1[i+1]word2[j+1]
class Solution {int miDist = INT32_MAX; //存储结果void lwstBT(std::string &a, std::string &b, int i, int j, int edist){if(i == a.size() || j  == b.size()){if(i < a.size()){edist += (int)(a.size() - i);}if(j < b.size()){edist += (int)(b.size() - j);}miDist = std::min(miDist, edist);return;}if(a[i] == b[j]){   // 两个字符匹配lwstBT(a, b, i + 1, j + 1, edist);}else{//两个字符不匹配lwstBT(a, b, i + 1,   j ,    edist + 1);  // 删除 a[i] 或者 b[j] 前添加一个字符lwstBT(a, b,   i,     j + 1, edist + 1);   // 删除 b[j] 或者 a[i] 前添加一个字符lwstBT(a, b, i + 1, j + 1, edist + 1);    // 将 a[i] 和 b[j] 替换为相同字符}}
public:int minDistance(string word1, string word2){lwstBT(word1, word2, 0,0, 0);return miDist;}
};

根据回溯算法的代码实现,我们可以画出递归树,看是否存在重复子问题。如果存在重复子问题,那我们就可以考虑是否能用动态规划来解决;如果不存在重复子问题,那回溯就是最好的解决方法。


在递归树中,每个节点代表一个状态,状态包含三个变量(i,j,edist)(i,j,edist)(i,j,edist),其中,edist表示处理到a[i]a[i]a[i]和b[j]b[j]b[j]时,已经执行的编辑操作的次数。

在递归树中,(i,j)(i,j)(i,j)两个变量重复的节点很多,比如(3,2)(3,2)(3,2)和(2,3)(2,3)(2,3)。对于(i,j)(i,j)(i,j)相同的节点,我们只需要考虑保留edist最小的,继续递归处理就可以了,剩下的节点都可以舍弃。所以,状态就从(i,j,edist)(i,j,edist)(i,j,edist)变成了(i,j,mindist)(i,j,mindist)(i,j,mindist),其中mindist表示处理到a[i]a[i]a[i]和b[j]b[j]b[j],已经执行的最少编辑次数。

这里的动态递归跟矩阵最短路径很相似,在矩阵最短路径中,到达状态(i,j)只能通过(i-1,j)或者(i,j-1)两个状态转移过来,而字符编辑距离状态(i,j)可能从(i-1,j),(i,j-1),(i-1,j-1)三个状态中的任意一个转移过来。

样本对应模型

样本对应模型往往是根据结尾位置来做可能性划分的

  • 定义dp[i][j]dp[i][j]dp[i][j]表示:s1只拿前i个字符,编辑成s2的前j个字符的最小代价

举个例子

(1)初始化表

  • dp[0][0]dp[0][0]dp[0][0]表示空串编辑成空串的最小代价
  • dp[0][1...]dp[0][1...]dp[0][1...]表示s1为空串时,编辑成s2的前j个前缀的最小代价
    • 空串—>非空串,那么只能添加字符
  • dp[1...][0]dp[1...][0]dp[1...][0]表示s2为空串时,取s1的前i个字符能编辑成空串的最小代价
    • 非空串—>空串,那么只能删除字符

(2)中间情况怎么决策

  • 假设前面的都解决了,现在正在考虑最后一个字符
  • 现在只要对s1的最后一个字符做些什么,就能将s1编辑成s2了。那么对于s1能做哪些动作呢?
    • 可能性1:s1的前i-1个字符已经变成了s2的前j个字符,现在只需要删除s1的第i个字符即可
    • 可能性2:s1的前i个字符已经变成了s2的前j-1个字符,现在只需要加上s2的第j个字符
    • 可能性3: s1、s2的最后一个字符相等,那么只需要s1的前i-1个字符变成s2的前j-1个字符即可
    • 可能性4:s1、s2的最后一个字符不相等,那么只需要s1的前i-1个字符变成s2的前j-1个字符即可,最后一个字符串做替换
    • 可能性3和可能性4只有一个能够成立
class Solution {private:int minCost(std::string s1, std::string s2, int ac, int dc, int rc){int M = s1.size(), N = s2.size() ;std::vector<std::vector<int>> dp(M + 1, std::vector<int>(N + 1, 0));dp[0][0] = 0;for (int i = 1; i <= M; ++i) {dp[i][0] = dc * i;  //非空串--->空串,只能删除字符}for (int j = 1; j <= N; ++j) {dp[0][j] = ac * j;  //空串--->非空串,只能添加字符}for (int i = 1; i <= M; ++i) {for (int j = 1; j <= N; ++j) {if(s1[i - 1] == s2[j - 1]){dp[i][j] = dp[i - 1][j - 1];}else{dp[i][j] = dp[i - 1][j - 1] + rc;}dp[i][j] = std::min(dp[i][j - 1] + dc, dp[i][j]);dp[i][j] = std::min(dp[i - 1][j] + ac, dp[i][j]);}}return dp[M][N];}public:int minDistance(string word1, string word2){return minCost(word1, word2, 1,1, 1);}
};

推导转移方程

(1)定义状态

  • f[i][j]表示将word1的前i个字符变成word2的前j个字符所需要进行的最少操作次数
  • 需要考虑 word1 或 word2 一个字母都没有,即全增加/删除的情况,所以预留dp[0][j]dp[i][0]

(2)状态转移方程:对于f[i][j],考虑word1的第i个字符与word2的第j个字符,分为两种情况:

  • word1[i] == word2[j],则f[i][j] == f[i - 1][j - 1]
  • word1[i] != word2[j],我们有三种选择,替换、删除、插入:
    • 替换: 替换word1的第i个字符或者替换word2的第j个字符,则f[i][j] == f[i - 1][j - 1] + 1
    • 删除: 删除word1的第i个字符或者删除word2的第j个字符,则f[i][j] = min(f[i - 1][j], f[i][j - 1]) + 1;
    • 插入: 在 word2[j] 后面添加 word1[i]或者在word1[i]后添加word2[j],则f[i][j] = min(f[i - 1][j], f[i][j - 1]) + 1;




(3)计算顺序:

  • 按顺序计算,当计算 dp[i][j] 时,dp[i - 1][j]dp[i][j - 1]dp[i - 1][j - 1]均已经确定了

(4)dp如何初始化:从一个字符串变成空字符串,非空字符串的长度就是编辑距离

for (int i = 0; i <= len1; i++) {dp[i][0] = i;
}for (int j = 0; j <= len2; j++) {dp[0][j] = j;
}


(5)考虑输出

  • 输出:dp[len1][len2] 符合语义,即 word1[0…len) 转换成 word2[0…len2) 的最小操作数

(6)空间优化:

  • 根据状态转移方程,当前要填写的单元格的数值,完全取决于它的左边一格、上边一格,左上边主对角线上一个的数值。如下图:

  • 因此,有两种经典的空间优化方案:① 滚动数组;② 把主对角线上要参考的数值使用一个新变量记录下来,然后在一维表格上循环赋值。由于空间问题不是这道题的瓶颈,可以不做这样的空间优化。
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));for (int i = 0; i <= m; ++i) dp[i][0] = i;for (int i = 0; i <= n; ++i) dp[0][i] = 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], min(dp[i - 1][j], dp[i][j - 1])) + 1;}}}return dp[m][n];}
};
  • 怎么填表
  • 关于动画

类似题目

题目 思路
leetcode:72. 编辑距离 Edit Distance
leetcode:1143. 最长公共子序列 Longest Increasing Subsequence 只关心str1[0…i],str2[0…j],对于它们的最长公共子序列长度是多少(样本对应模型,往往以考虑结尾来组织可能性)
leetcode:583. 使得两个字符相同的最少删除次数 Delete Operation for Two Strings
leetcode:712. 使得两个字符相同时所需删除字符最小ASCII值和 Minimum ASCII Delete Sum for Two Strings

leetcode:72. 编辑距离相关推荐

  1. [leetcode] 72. 编辑距离(二维动态规划)

    72. 编辑距离 再次验证leetcode的评判机有问题啊!同样的代码,第一次提交超时,第二次提交就通过了! 此题用动态规划解决. 这题一开始还真难到我了,琢磨半天没有思路.于是乎去了网上喵了下题解看 ...

  2. Java实现 LeetCode 72 编辑距离

    72. 编辑距离 给定两个单词 word1 和 word2,计算出将 word1 转换成 word2 所使用的最少操作数 . 你可以对一个单词进行如下三种操作: 插入一个字符 删除一个字符 替换一个字 ...

  3. LeetCode 72. 编辑距离(DP)

    1. 题目 给定两个单词 word1 和 word2,计算出将 word1 转换成 word2 所使用的最少操作数 . 你可以对一个单词进行如下三种操作: 插入一个字符 删除一个字符 替换一个字符 示 ...

  4. 2022-3-22 Leetcode 72.编辑距离

    class Solution {public:int minDistance(string word1, string word2) {int n = word1.length();int m = w ...

  5. 123. Leetcode 72. 编辑距离 (动态规划- 字符串系列)

    步骤一.确定状态: 确定dp数组及下标含义 dp[i][j]表示word1[:i]的单词与word2[:j]单词之间的最小编辑距离 步骤二.推断状态方程: 在确定递推公式的时候,首先要考虑清楚编辑的几 ...

  6. LeetCode 72.编辑距离(动态规划)

    编辑距离 针对两个字符串(如英文字母)的差异程度的量化测量,量测方式是看至少需要多少次的处理才能将一个字符串变成另一个字符串. 编辑距离应用 一.可应用于NLP,如拼写检查可以根据一个拼错的字和其他正 ...

  7. leetcode 72. 编辑距离

    /***** 定义状态: DP[i][j]其中i表示word1前i个字符,j表示Word2前i个字符 DP[i][j]表示单词1前i个字符匹配单词2前j个字符,最少变换次数: 状态转移: for i: ...

  8. [leetcode] 72.编辑距离

    给你两个单词 word1 和 word2,请你计算出将 word1 转换成 word2 所使用的最少操作数 . 你可以对一个单词进行如下三种操作: 插入一个字符 删除一个字符 替换一个字符 示例 1: ...

  9. LeetCode 72 编辑距离

    思路:动态规划 class Solution { public:int minDistance(string word1, string word2) { int dp[501][501]; for( ...

  10. ⭐算法入门⭐《动态规划 - 串匹配》困难02 —— LeetCode 72. 编辑距离

最新文章

  1. WijmoJS 2019V1正式发布:全新的在线 Demo 系统,助您快速上手,开发无忧
  2. fasterrcnn深度学习口罩检测
  3. MySQL多源复制【转】
  4. 基类和派生类中使用static_cast和dynamic_cast进行类型转换
  5. Ubuntu设置宽带连接DSL
  6. Python——蟒蛇绘制
  7. zigbee的路由器能分配网络地址吗_真核细胞无丝分裂能将核DNA精准的平均分配到两个子细胞中吗?...
  8. delphi xe3 mysql,delphi10.3安装使用mySQL
  9. 在线世界地图生成器 pixelmap可调色
  10. mysql 1.4安装步骤_1.4.1 MySql安装配置
  11. 母函数(指数型)(泰勒展开式)
  12. Android局域网工具,局域网内连接Android进行调试
  13. 染色问题java_java地图路径染色寻找算法
  14. 以春雨为题写一篇500字的散文
  15. 智能开关继电器-选型篇2
  16. 使用Vue写一个登录页面
  17. 通过百度语音生成音频文件
  18. Mac 终端打开jupyter notebook,用这句话就对了
  19. Docker USER 指定当前用户
  20. python的星号(*)和双星号(**)运算符的使用

热门文章

  1. 三体·Round - 智子(Easy Version)
  2. 微服务架构实战第一节 Spring Cloud介绍
  3. linux服务器下tomcat版本升级
  4. 《脱颖而出——成功网店经营之道》一2.4 可持续化发展
  5. 解决jsqlparser 依赖版本冲突
  6. 关于Node.js的httpClieint请求报错ECONNRESET的原因和解决措施
  7. Linux sed神器
  8. Idea设置JDK版本(在jdk版本超过java8时,使用Hutool工具包获取系统信息报错)
  9. 2022年塔式起重机安装拆卸工(建筑特殊工种)考试试题及模拟考试
  10. AL3220光感调试记录