学习资料:代码随想录
115.不同的子序列
递推公式:求的是个数而不是长度,dp[i-1][j]代表的是用i-2为结尾的s的子序列去能凑出j-1为结尾的t的子序列的方法数,代表的是dp[i][j]的上一状态,当s[i-1]==t[i-1],说明可以从dp[i-1][j-1]的代表的用i-2为结尾的s的子序列去能凑出j-2为结尾的t的子序列的方法数方法数状态各加一个数抵达dp[i][j]代表的方法数的状态,不仅如此,dp[i-1][j]表示i-2为结尾的s就能凑出j-1为结尾的t的方法数,无论s[i-1]==t[i-1]满足不满足,那s以i-1为结尾凑t以j-1为结尾的方法数dp[i][j]肯定包括dp[i-1][j]
简单理解就是:相等时之前就能盛的方法加上刚能盛,,不相等时等于之前能盛的方法
// 定义:dp[i][j]表示以i-1为结尾的s的子序列中出现以j-1为结尾的t的子序列的个数
// 递推公式:求的是个数而不是长度,dp[i-1][j]代表的是用i-2为结尾的s的子序列去能凑出j-1为结尾的t的子序列的方法数,代表的是dp[i][j]的上一状态,当s[i-1]==t[i-1],说明可以从dp[i-1][j-1]的代表的用i-2为结尾的s的子序列去能凑出j-2为结尾的t的子序列的方法数方法数状态各加一个数抵达dp[i][j]代表的方法数的状态,不仅如此,dp[i-1][j]i-2为结尾的s就能凑出j-1为结尾的t的方法数,无论s[i-1]==t[i-1]满足不满足,那s以i-1为结尾凑t以j-1为结尾的方法数dp[i][j]肯定包括dp[i-1][j]
// 初始化:其实还是举例子往前推一两步合适,理解上,dp[i][0]是t的子序列为空字符的状态,所以初始化为1,dp[0][j]是s的子序列为空字符的状态,所以初始化为0. dp[0][0],空字符凑空字符的方法数为0
// 遍历顺序:
// 打印:
class Solution {
public:
int numDistinct(string s, string t) {
vector<vector<uint64_t>> dp(s.size()+1,vector<uint64_t>(t.size()+1,0));
for(int i=0;i<=s.size();i++){
dp[i][0] = 1;
}
for(int j=0;j<=t.size();j++){
dp[0][j] = 0;
}
dp[0][0] = 1;
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()];
}
};
583. 两个字符串的删除操作
关于对递推公式的理解上的一点思考:
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][j - 1] + 1);
}
}
结合卡哥的讲解,在结合定义理解一下递推公式
dp[i][j]:使word1的以i-1为结尾的子序列和word2的j-1为结尾的子序列相等所需要的最少删减次数
dp[i-1][j]:使word1的以i-2为结尾的子序列和word2的j-1为结尾的子序列相等所需要的最少删减次数,当word[i-1]!=word[j-1],要作删元素的操作了,所以是+1,word1删一个或者word2删一个,dp[i-1][j]和dp[i][j-1]是删之前的最小操作数,
dp[i-1][j-1]+2可以不出现在递推公式里是因为已经包括在前两个里面了
// 定义:dp[i][j]表示使word1的以i-1为结尾的子序列和word2的j-1为结尾的子序列相等所需要的最少删减次数
// 递推公式:还是看word1[i-1]和word2[j-1]是否相等,如果相等的话不用再删了,如果不相等,就需要删了,删一个就可以,从哪个数组中删取决于从哪个数组中删得到的操作步数是最少的,所以要比较一下删i-1(对应dp[i-1][j]+1),还是删j-1(对应dp[i][j-1]+1)
// 初始化:对于dp[i][0],只有全删了才能配上,所以最小操作数是i,dp[j][0]同理,还是找个例子模拟一下合适
// 遍历顺序:看递推公式是什么方向
// 打印
class Solution {
public:
int minDistance(string word1, string word2) {
vector<vector<int>> dp(word1.size()+1,vector<int>(word2.size()+1));
for(int i=0;i<=word1.size();i++){dp[i][0]=i;} //i=1是,对应word1[0],word1的第一个数
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][j-1]+1);
}
}
}
return dp[word1.size()][word2.size()];
}
};
方法二:求最长公共子序列再求需要删减的数量
// 定义:dp[i][j]表示使word1的以i-1为结尾的子序列和word2的j-1为结尾的最长相等子序列
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=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]+1;
}
else{
dp[i][j] = max(dp[i-1][j],dp[i][j-1]);
}
}
}
int size = dp[word1.size()][word2.size()];
int result = 0;
result = word1.size()+word2.size()-2*size;
return result;
}
};
72. 编辑距离
这道题能不能不叫编辑距离,叫最小编辑数
递推公式:word1[i-1]==word2[j-1]时就还是等于dp[i][j],不相等时,增操作和减操作体现在递推公式上是一样的,都是删或增i-1,删或增j-1; 替换操作,word1替换word1[i - 1],使其与word2[j - 1]相同,此时不用增删加元素,那么只需要一次替换i-1或j-1的操作,就可以让 word1[i - 1] 和 word2[j - 1] 相同
// 定义:dp[i][j]表示单词word1以i-1为结尾,单词word2以j-1为结尾,两个单词相等需要的最小操作数
// 递推公式:word1[i-1]==word2[j-1]时就还是等于dp[i][j],不相等时,增操作和减操作体现在递推公式上是一样的,都是删或增i-1,删或增j-1; 替换操作,word1替换word1[i - 1],使其与word2[j - 1]相同,此时不用增删加元素,那么只需要一次替换i-1或j-1的操作,就可以让 word1[i - 1] 和 word2[j - 1] 相同
//初始化:dp[i][0]和dp[0][j]还是要分别等于i和j
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],dp[i][j-1],dp[i-1][j-1]})+1;
}
}
}
return dp[word1.size()][word2.size()];
}