115. 不同的子序列
题目描述
给定一个字符串 s
和一个字符串 t
,计算在 s
的子序列中 t
出现的个数。
字符串的一个 子序列 是指,通过删除一些(也可以不删除)字符且不干扰剩余字符相对位置所组成的新字符串。(例如,"ACE"
是 "ABCDE"
的一个子序列,而 "AEC"
不是)
题目数据保证答案符合 32 位带符号整数范围。
示例 1:
输入:s = "rabbbit", t = "rabbit" 输出:3 解释: 如下图所示, 有 3 种可以从 s 中得到 "rabbit" 的方案。 rabbbit rabbbit rabbbit
示例 2:
输入:s = "babgbag", t = "bag" 输出:5 解释: 如下图所示, 有 5 种可以从 s 中得到 "bag" 的方案。 babgbag babgbag babgbag babgbag babgbag
思路分析
这道题目如果不是子序列,而是要求连续序列的,那就可以考虑用KMP。
动规五部曲
确定dp数组以及下标的含义
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[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];
dp数组如何初始化
从递推公式dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j]; 和 dp[i][j] = dp[i - 1][j];中可以看出dp[i] [0] 和dp[0] [j]是一定要初始化的。
dp[i] [0] 表示:以i-1为结尾的s可以随便删除元素,出现空字符串的个数。
那么dp[i] [0]一定都是1,因为也就是把以i-1为结尾的s,删除所有元素,出现空字符串的个数就是1。
dp[0] [j]:空字符串s可以随便删除元素,出现以j-1为结尾的字符串t的个数。
那么dp[0] [j]一定都是0,s如论如何也变成不了t。
最后就要看一个特殊位置了,即:dp[0] [0] 应该是多少。
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数组
以s:“baegg”,t:"bag"为例,推导dp数组状态如下:
参考代码
#include<bits/stdc++.h> using namespace std; //动态规划法 int numDistinct(string s, string t) { vector<vector<uint64_t>> dp(s.size()+1,vector<uint64_t>(t.size()+1,0));//dp定义 uint64_t:无符号的long int //初始化 dp[i][j]:以i-1为结尾的s子序列中出现以j-1为结尾的t的个数为dp[i][j]。 //dp[0][j] = 0 dp[i][0] = 1:当s没有元素便是答案 dp[0][0] = 1 for(int i = 0; i <= s.size(); i++) { dp[i][0] = 1; } //递推dp 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]; //用s[i - 1]+不用s[i - 1]匹配 }else{ dp[i][j] = dp[i-1][j];//如果不相等,则s中删除元素 . 用s[i-2] 匹配. } } } return dp[s.size()][t.size()] ; }
583. 两个字符串的删除操作
题目描述
给定两个单词 word1
和 word2
,返回使得 word1
和 word2
相同所需的最小步数。
每步 可以删除任意一个字符串中的一个字符。
示例 1:
输入: word1 = "sea", word2 = "eat" 输出: 2 解释: 第一步将 "sea" 变为 "ea" ,第二步将 "eat "变为 "ea"
示例 2:
输入:word1 = "leetcode", word2 = "etco" 输出:4
思路分析
这个题相比上题,就是两个字符串都可以进行删除了,清空会稍微复杂一点,但是整体思路保持不变.
动规五部曲
确定dp数组以及下标的含义
dp[i] [j]:以i-1结尾的字符串word1和以j-1结尾的字符串word2,想要达到相等,所要删除元素的最少次数.
确定递推公式
当word1[i-1]与word2[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});
dp数组如何初始化
从递推公式可以看出来,dp[i] [0]和dp[0] [j]是一定要初始化的.
dp[i][0]:word2为空字符串,以i-1为结尾的字符串word1要删除多少个元素,才能和word2相同呢,很明显dp[i][0] = i。
确定遍历顺序
从递推公式可有看出来,dp[i] [j]是根据左上方,正上方,正左方推出来的.
所以遍历的时候一定是 从上到下,从左到右.
举例推导dp数组
以word1:“sea”,word2:"eat"为例,推导dp数组状态图如下:
参考代码
#include<bits/stdc++.h> using namespace std; int minDistance(string word1, string word2) { vector<vector<int>> dp(word1.size()+1,vector<int>(word2.size()+1,0));//定义dp //初始化dp dp[0][j] = j dp[i][0] = i dp[0][0] = 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; } //递推dp 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]+2,dp[i-1][j]+1,dp[i][j-1]+1});//word1,word2都删除, word1删除,word2删除. 取个最小值 } } } return dp[word1.size()][word2.size()]; }
以上相关题解来源于卡尔大佬的代码随想录
如果有收获!!! 希望老铁们来个三连,点赞、收藏、转发。
创作不易,别忘点个赞,可以让更多的人看到这篇文章,顺便鼓励我写出更好的博客