115. 不同的子序列, 大彻大悟
✅做题思路or感想
这道困扰我一刷好久的题终于在二刷的今天彻底理解了,通透啊!
这道题的难点在于递推公式和初始化
首先可以把题目按照自己的意思更大白话的复述一遍
- 题面:找
s的子序列中t出现的次数 - 复述:在
s中挑选字符来组成t,求这个操作有几种方法
dp数组含义
dp[i][j]表示[0, i - 1]和[0, j - 1]的区间上的挑选字符使之匹配的方法个数
💡递推公式
这里从最后一步开始思考。以s = baba,t = ba为例子,当最后一个字符a相同时,有两种选择(从最后一步思考)
s最后一位可以把t的最后一位匹配掉,则下一步要缩短匹配范围,去匹配bab和b,即有dp[i - 1][j - 1]- 因为
s是长的一方,s里面除了最后一位外可能还有其他的a和t的最后一位a匹配,所以这里放弃s的最后一位,缩短s的搜索范围继续匹配t的最后一位,所以有dp[i - 1][j] - 最后因为求的是方法数,所以要把这两种可能都加起来,故有
dp[i][j] = dp[i - 1][j - 1], dp[i - 1][j]
而当s最后一个字符匹配不掉t的最后一个字符,则s再向后面匹配便是了,故有dp[i][j] = dp[i - 1][j]
综上
if (s[i - 1] == t[j - 1]) {
dp[i][j] = dp[i - 1][j - 1] //s[i - 1]匹配掉了t[j - 1]
+ dp[i - 1][j]; //s[i - 1]没有匹配掉t[j - 1]
} else {
dp[i][j] = dp[i - 1][j]; //s[i - 1]没有匹配掉t[j - 1]
}
初始化
这种字符串问题的初始化一定要联想两边是空字符串的时候的情况
s是空字符串时,从s中挑不出字符组成t,所以这种情况的方法数为0,即是dp[0][j] = 0。而例外就是如果t也是空字符串,则有一种方法可以让他们匹配:什么也不做(认真),所以dp[0][0] = 1,这点特别重要t是空字符串时,从s中挑字符组成t只有一种方法:什么也不做(认真),所以这种情况的方法数为1,即dp[i][0] = 1
遍历顺序
从递推公式知从小推大,故正序遍历
class Solution {
public:
int numDistinct(string s, string t) {
//这里注意测试用例里有超出int范围的例子,所以这里扩大一下范围,选用unsigned int
vector<vector<unsigned>>dp (s.size() + 1, vector<unsigned>(t.size() + 1, 0));
//初始化dp[i][0] = 1的情况,另一种情况在创建数组的时候就默认为0了,所以不用再用一个for来初始化
for (int i = 0; i < s.size(); ++i) {
dp[i][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()];
}
};
这一题能理解透彻真的不容易,大彻大悟的感觉就是舒服

浙公网安备 33010602011771号