目录
115.不同的子序列
动态规划法
72.编辑距离 ⭐
动态规划法
115.不同的子序列
-
题目链接:115. 不同的子序列 - 力扣(LeetCode)
-
文章讲解:代码随想录
动态规划法
-
解题思路
-
这道题目如果不是子序列,而是要求连续序列的,那就可以考虑用KMP。这道题目相对于72. 编辑距离,简单了不少,因为本题相当于只有删除操作,不用考虑替换增加之类的。但相对于动态规划:392.判断子序列 (opens new window)就有难度了
-
-
解题步骤
当s[i - 1] 与 t[j - 1]相等时,dp[i][j]可以有两部分组成。一部分是用s[i - 1]来匹配,那么个数为dp[i - 1][j - 1]。即不需要考虑当前s子串和t子串的最后一位字母,所以只需要 dp[i-1][j-1]。一部分是不用s[i - 1]来匹配,个数为dp[i - 1][j]。所以当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]来匹配(就是模拟在s中删除这个元素),即:dp[i - 1][j]。所以递推公式为:dp[i][j] = dp[i - 1][j];
-
确定dp数组(dp table)以及下标的含义:dp[i][j]:以i-1为结尾的s子序列中出现以j-1为结尾的t的个数为dp[i][j]。
-
确定递推公式
-
s[i - 1] 与 t[j - 1]相等
-
s[i - 1] 与 t[j - 1] 不相等
-
-
dp数组如何初始化:从递推公式dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j]; 和 dp[i][j] = dp[i - 1][j]; 中可以看出dp[i][j] 是从上方和左上方推导而来,如图:,那么 dp[i][0] 和dp[0][j]是一定要初始化的。那么dp[i][0]一定都是1,因为也就是把以i-1为结尾的s,删除所有元素,出现空字符串的个数就是1。dp[0][j],dp[0][j]:空字符串s可以随便删除元素,出现以j-1为结尾的字符串t的个数。dp[0][j]一定都是0,s如论如何也变成不了t。最后就要看一个特殊位置了,dp[0][0]应该是1,空字符串s,可以删除0个元素,变成空字符串t。
-
确定遍历顺序:从递推公式dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j]; 和 dp[i][j] = dp[i - 1][j]; 中可以看出dp[i][j]都是根据左上方和正上方推出来的。所以遍历的时候一定是从上到下,从左到右,这样保证dp[i][j]可以根据之前计算出来的数值进行计算。
-
举例推导dp数组:
-
-
代码一:动态规划
class Solution {
public:int numDistinct(string s, string t) {vector<vector<uint64_t>> dp(s.size() + 1, vector<uint64_t>(t.size() + 1));for (int i = 0; i < s.size(); i++) dp[i][0] = 1;for (int j = 1; j < t.size(); j++) dp[0][j] = 0;for (int i = 1; i <= s.size(); i++) {for (int j = 1; j <= t.size(); j++) {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];}}}return dp[s.size()][t.size()];}
};
72.编辑距离 ⭐
-
题目链接:72. 编辑距离 - 力扣(LeetCode)
-
文章讲解:代码随想录
动态规划法
-
解题思路
-
编辑距离是用动规来解决的经典题目,这道题目看上去好像很复杂,但用动规可以很巧妙的算出最少编辑距离。
-
-
解题步骤
在整个动规的过程中,最为关键就是正确理解dp[i][j]
的定义!if (word1[i - 1] != word2[j - 1])
,此时就需要编辑了-
操作一:word1删除一个元素,那么就是以下标i - 2为结尾的word1 与 j-1为结尾的word2的最近编辑距离 再加上一个操作。即
dp[i][j] = dp[i - 1][j] + 1;
-
操作二:word2删除一个元素,那么就是以下标i - 1为结尾的word1 与 j-2为结尾的word2的最近编辑距离 再加上一个操作。即
dp[i][j] = dp[i][j - 1] + 1;
-
操作三:替换元素,
word1
替换word1[i - 1]
,使其与word2[j - 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;
-
确定dp数组(dp table)以及下标的含义:dp[i][j] 表示以下标i-1为结尾的字符串word1,和以下标j-1为结尾的字符串word2,最近编辑距离为dp[i][j]。
-
if (word1[i - 1] == word2[j - 1])
那么说明不用任何编辑,dp[i][j]
就应该是dp[i - 1][j - 1]
,即dp[i][j] = dp[i - 1][j - 1];word1[i - 1]
与word2[j - 1]
相等了,那么就不用编辑了,以下标i-2为结尾的字符串word1和以下标j-2为结尾的字符串word2
的最近编辑距离dp[i - 1][j - 1]
就是dp[i][j]
了。
-
-
dp数组如何初始化:再回顾一下dp[i][j]的定义:dp[i][j] 表示以下标i-1为结尾的字符串word1,和以下标j-1为结尾的字符串word2,最近编辑距离为dp[i][j]。dp[i][0] :以下标i-1为结尾的字符串word1,和空字符串word2,最近编辑距离为dp[i][0]。那么dp[i][0]就应该是i,对word1里的元素全部做删除操作,即:dp[i][0] = i;同理dp[0][j] = j;
-
确定遍历顺序
从如下四个递推公式:-
dp[i][j] = dp[i - 1][j - 1]
-
dp[i][j] = dp[i - 1][j - 1] + 1
-
dp[i][j] = dp[i][j - 1] + 1
-
dp[i][j] = dp[i - 1][j] + 1
-
-
举例推导dp数组:
-
-
代码一:动态规划
class Solution {
public:int minDistance(string word1, string word2) {vector<vector<int>> dp(word1.size() + 1, vector<int>(word2.size() + 1, 0));for (int i = 0; i <= word1.size(); i++) dp[i][0] = i;for (int j = 0; j <= word2.size(); j++) dp[0][j] = j;for (int i = 1; i <= word1.size(); i++) {for (int j = 1; j <= word2.size(); 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], dp[i - 1][j], dp[i][j - 1]}) + 1;}}}return dp[word1.size()][word2.size()];}
};