mirror of
https://github.com/youngyangyang04/leetcode-master.git
synced 2026-02-02 18:39:09 +08:00
更新动态规划专题Markdown文件
This commit is contained in:
158
problems/为了绝杀编辑距离,卡尔做了三步铺垫.md
Normal file
158
problems/为了绝杀编辑距离,卡尔做了三步铺垫.md
Normal file
@@ -0,0 +1,158 @@
|
||||
|
||||
# 动态规划之编辑距离总结篇
|
||||
|
||||
本周我们讲了动态规划之终极绝杀:编辑距离,为什么叫做终极绝杀呢?
|
||||
|
||||
细心的录友应该知道,我们在前三篇动态规划的文章就一直为 编辑距离 这道题目做铺垫。
|
||||
|
||||
## 判断子序列
|
||||
|
||||
[动态规划:392.判断子序列](https://mp.weixin.qq.com/s/2pjT4B4fjfOx5iB6N6xyng) 给定字符串 s 和 t ,判断 s 是否为 t 的子序列。
|
||||
|
||||
|
||||
这道题目 其实是可以用双指针或者贪心的的,但是我在开篇的时候就说了这是编辑距离的入门题目,因为从题意中我们也可以发现,只需要计算删除的情况,不用考虑增加和替换的情况。
|
||||
|
||||
* if (s[i - 1] == t[j - 1])
|
||||
* t中找到了一个字符在s中也出现了
|
||||
* if (s[i - 1] != t[j - 1])
|
||||
* 相当于t要删除元素,继续匹配
|
||||
|
||||
状态转移方程:
|
||||
|
||||
```
|
||||
if (s[i - 1] == t[j - 1]) dp[i][j] = dp[i - 1][j - 1] + 1;
|
||||
else dp[i][j] = dp[i][j - 1];
|
||||
```
|
||||
|
||||
## 不同的子序列
|
||||
|
||||
[动态规划:115.不同的子序列](https://mp.weixin.qq.com/s/1SULY2XVSROtk_hsoVLu8A) 给定一个字符串 s 和一个字符串 t ,计算在 s 的子序列中 t 出现的个数。
|
||||
|
||||
本题虽然也只有删除操作,不用考虑替换增加之类的,但相对于[动态规划:392.判断子序列](https://mp.weixin.qq.com/s/2pjT4B4fjfOx5iB6N6xyng)就有难度了,这道题目双指针法可就做不了。
|
||||
|
||||
|
||||
当s[i - 1] 与 t[j - 1]相等时,dp[i][j]可以有两部分组成。
|
||||
|
||||
一部分是用s[i - 1]来匹配,那么个数为dp[i - 1][j - 1]。
|
||||
|
||||
一部分是不用s[i - 1]来匹配,个数为dp[i - 1][j]。
|
||||
|
||||
这里可能有同学不明白了,为什么还要考虑 不用s[i - 1]来匹配,都相同了指定要匹配啊。
|
||||
|
||||
例如: s:bagg 和 t:bag ,s[3] 和 t[2]是相同的,但是字符串s也可以不用s[3]来匹配,即用s[0]s[1]s[2]组成的bag。
|
||||
|
||||
当然也可以用s[3]来匹配,即:s[0]s[1]s[3]组成的bag。
|
||||
|
||||
所以当s[i - 1] 与 t[j - 1]相等时,dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];
|
||||
|
||||
当s[i - 1] 与 t[j - 1]不相等时,dp[i][j]只有一部分组成,不用s[i - 1]来匹配,即:dp[i - 1][j]
|
||||
|
||||
所以递推公式为:dp[i][j] = dp[i - 1][j];
|
||||
|
||||
|
||||
状态转移方程:
|
||||
```C++
|
||||
if (s[i - 1] == t[j - 1]) {
|
||||
dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];
|
||||
} else {
|
||||
dp[i][j] = dp[i - 1][j];
|
||||
}
|
||||
```
|
||||
|
||||
## 两个字符串的删除操作
|
||||
|
||||
[动态规划:583.两个字符串的删除操作](https://mp.weixin.qq.com/s/a8BerpqSf76DCqkPDJrpYg)给定两个单词 word1 和 word2,找到使得 word1 和 word2 相同所需的最小步数,每步可以删除任意一个字符串中的一个字符。
|
||||
|
||||
本题和[动态规划:115.不同的子序列](https://mp.weixin.qq.com/s/1SULY2XVSROtk_hsoVLu8A)相比,其实就是两个字符串可以都可以删除了,情况虽说复杂一些,但整体思路是不变的。
|
||||
|
||||
|
||||
* 当word1[i - 1] 与 word2[j - 1]相同的时候
|
||||
* 当word1[i - 1] 与 word2[j - 1]不相同的时候
|
||||
|
||||
当word1[i - 1] 与 word2[j - 1]相同的时候,dp[i][j] = dp[i - 1][j - 1];
|
||||
|
||||
当word1[i - 1] 与 word2[j - 1]不相同的时候,有三种情况:
|
||||
|
||||
情况一:删word1[i - 1],最少操作次数为dp[i - 1][j] + 1
|
||||
|
||||
情况二:删word2[j - 1],最少操作次数为dp[i][j - 1] + 1
|
||||
|
||||
情况三:同时删word1[i - 1]和word2[j - 1],操作的最少次数为dp[i - 1][j - 1] + 2
|
||||
|
||||
那最后当然是取最小值,所以当word1[i - 1] 与 word2[j - 1]不相同的时候,递推公式:dp[i][j] = min({dp[i - 1][j - 1] + 2, dp[i - 1][j] + 1, dp[i][j - 1] + 1});
|
||||
|
||||
状态转移方程:
|
||||
```C++
|
||||
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] + 2, dp[i - 1][j] + 1, dp[i][j - 1] + 1});
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## 编辑距离
|
||||
|
||||
[动态规划:72.编辑距离](https://mp.weixin.qq.com/s/8aG71XjSgZG6kZbiAdkJnQ) 给你两个单词 word1 和 word2,请你计算出将 word1 转换成 word2 所使用的最少操作数 。
|
||||
|
||||
|
||||
编辑距离终于来了,**有了前面三道题目的铺垫,应该有思路了**,本题是两个字符串可以增删改,比 [动态规划:判断子序列](https://mp.weixin.qq.com/s/2pjT4B4fjfOx5iB6N6xyng),[动态规划:不同的子序列](https://mp.weixin.qq.com/s/1SULY2XVSROtk_hsoVLu8A),[动态规划:两个字符串的删除操作](https://mp.weixin.qq.com/s/a8BerpqSf76DCqkPDJrpYg)都要复杂的多。
|
||||
|
||||
|
||||
在确定递推公式的时候,首先要考虑清楚编辑的几种操作,整理如下:
|
||||
|
||||
* if (word1[i - 1] == word2[j - 1])
|
||||
* 不操作
|
||||
* if (word1[i - 1] != word2[j - 1])
|
||||
* 增
|
||||
* 删
|
||||
* 换
|
||||
|
||||
也就是如上四种情况。
|
||||
|
||||
if (word1[i - 1] == word2[j - 1]) 那么说明不用任何编辑,dp[i][j] 就应该是 dp[i - 1][j - 1],即dp[i][j] = dp[i - 1][j - 1];
|
||||
|
||||
此时可能有同学有点不明白,为啥要即dp[i][j] = dp[i - 1][j - 1]呢?
|
||||
|
||||
那么就在回顾上面讲过的dp[i][j]的定义,word1[i - 1] 与 word2[j - 1]相等了,那么就不用编辑了,以下标i-2为结尾的字符串word1和以下标j-2为结尾的字符串word2的最近编辑距离dp[i - 1][j - 1] 就是 dp[i][j]了。
|
||||
|
||||
在下面的讲解中,如果哪里看不懂,就回想一下dp[i][j]的定义,就明白了。
|
||||
|
||||
**在整个动规的过程中,最为关键就是正确理解dp[i][j]的定义!**
|
||||
|
||||
if (word1[i - 1] != word2[j - 1]),此时就需要编辑了,如何编辑呢?
|
||||
|
||||
操作一:word1增加一个元素,使其word1[i - 1]与word2[j - 1]相同,那么就是以下标i-2为结尾的word1 与 i-1为结尾的word2的最近编辑距离 加上一个增加元素的操作。
|
||||
|
||||
即 dp[i][j] = dp[i - 1][j] + 1;
|
||||
|
||||
|
||||
操作二:word2添加一个元素,使其word1[i - 1]与word2[j - 1]相同,那么就是以下标i-1为结尾的word1 与 j-2为结尾的word2的最近编辑距离 加上一个增加元素的操作。
|
||||
|
||||
即 dp[i][j] = dp[i][j - 1] + 1;
|
||||
|
||||
这里有同学发现了,怎么都是添加元素,删除元素去哪了。
|
||||
|
||||
**word2添加一个元素,相当于word1删除一个元素**,例如 word1 = "ad" ,word2 = "a",word2添加一个元素d,也就是相当于word1删除一个元素d,操作数是一样!
|
||||
|
||||
操作三:替换元素,word1替换word1[i - 1],使其与word2[j - 1]相同,此时不用增加元素,那么以下标i-2为结尾的word1 与 j-2为结尾的word2的最近编辑距离 加上一个替换元素的操作。
|
||||
|
||||
即 dp[i][j] = dp[i - 1][j - 1] + 1;
|
||||
|
||||
综上,当 if (word1[i - 1] != word2[j - 1]) 时取最小的,即:dp[i][j] = min({dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1]}) + 1;
|
||||
|
||||
递归公式代码如下:
|
||||
|
||||
```C++
|
||||
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 - 1][j], dp[i][j - 1]}) + 1;
|
||||
}
|
||||
```
|
||||
|
||||
## 总结
|
||||
|
||||
心思的录友应该会发现我用了三道题做铺垫,才最后引出了[动态规划:72.编辑距离](https://mp.weixin.qq.com/s/8aG71XjSgZG6kZbiAdkJnQ) ,Carl的良苦用心呀,你们体会到了嘛!
|
||||
|
||||
Reference in New Issue
Block a user