这道困扰我一刷好久的题终于在二刷的今天彻底理解了,通透啊!
首先可以把题目按照自己的意思更大白话的复述一遍
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()];
}
};
这一题能理解透彻真的不容易,大彻大悟的感觉就是舒服