题目描述
给定一个字符串 S 和一个字符串 T,计算在 S 的子序列中 T 出现的个数。
一个字符串的一个子序列是指,通过删除一些(也可以不删除)字符且不干扰剩余字符相对位置所组成的新字符串。(例如,“ACE” 是 “ABCDE” 的一个子序列,而 “AEC” 不是)
示例 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
^^^
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/distinct-subsequences
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
错误的思路
一开始是想先求最长相同子序列LCS,然后根据LCS的DP数组回溯找路径,试了下,有三分之一的样例超时
这里给出超时代码,听个响就好
class Solution {
public:
int ans = 0;
void dfs(vector<vector<int>>& dp, int x, int y, int len, string& s, string& t)
{
if(x<1 || y<1) return;
if(len==0 && s[x]==t[1]) {ans++;}
if(s[x]==t[y])
{
dfs(dp, x-1, y-1, len-1, s, t);
if(dp[x][y]==dp[x-1][y]) dfs(dp, x-1, y, len, s, t);
if(dp[x][y]==dp[x][y-1]) dfs(dp, x, y-1, len, s, t);
}
else if(dp[x-1][y]>dp[x][y-1]) dfs(dp, x-1, y, len, s, t);
else if(dp[x-1][y]<dp[x][y-1]) dfs(dp, x, y-1, len, s, t);
else if(dp[x-1][y]==dp[x][y-1])
{
dfs(dp, x-1, y, len, s, t);
dfs(dp, x, y-1, len, s, t);
}
}
int numDistinct(string s, string t)
{
if(s.length()<t.length()) return 0;
if(t.length()==0) return 1;
s.insert(s.begin(), '#'); t.insert(t.begin(), '#');
vector<vector<int>> dp(s.length()+1);
for(int i=0; i<s.length(); i++) dp[i]=vector<int>(t.length()+1);
for(int i=1; i<s.length(); i++)
for(int j=1; j<t.length(); j++)
if(s[i]==t[j]) dp[i][j]=dp[i-1][j-1]+1;
else dp[i][j]=max(dp[i-1][j], dp[i][j-1]);
if(dp[s.length()-1][t.length()-1]!=t.length()-1) return 0;
dfs(dp, s.length()-1, t.length()-1, t.length()-2, s, t);
return ans;
}
};
正确的思路
不过正是因为自己一开始不知道干嘛求了个LCS,其实上面dfs的时候,是没怎么用到dp数组的值的,对于这个问题我们应该使用动态规划
观察路径我们发现:
- 向左上方走,表示两个串局部匹配了,往前看
- 往上方走,表示两个串不匹配,那么尝试跳过s串的部分字符,也就是向上走
- 没有向左走,因为不允许我们跳过t串的字符
其实每次就是往上和往左了,那么我们往左上走的次数为t串的长度时,就说明我们找完了!
走法:
- 对于两串某位置相同,我们可以向左上走,也就是同时跳过相同的字符,也可以向上走,即选择跳过这个字符,因为前面还可能有相同的
- 对于两串某位置不同,我们只能向上走,即即选择跳过这个字符,尝试在前方寻找匹配
或者再抽象一点,问题是在s串中删除一定数量的字符,使得s串变成t串,问一共多少种方法?
可以这么理解:向上走就是删除,向左上走就是匹配上了
那么状态转移就出来了:
约定:我们在s,t串的前头加上一个字符,把他看成空,这样方便处理空串的情况
dp[i][j]表示 【s串的1到i下标的子串】有多少种方法变为【t串的1到j下标的子串】
状态转移:
if(s[i]==t[j]) dp[i][j] = dp[i-1][j-1] + dp[i-1][j];
else if(s[i]!=t[j]) dp[i][j] = dp[i-1][j];
初始状态:
dp[任意][0] = 1; 表示任意长度的串变成空串只有一种方法,就是删光
代码
class Solution {
public:
int numDistinct(string s, string t)
{
if(s.length()<t.length()) return 0;
s.insert(s.begin(), '#'); t.insert(t.begin(), '#');
long long dp[s.length()+1][t.length()+1]; memset(dp, 0, sizeof(dp));
for(int i=0; i<s.length(); i++) dp[i][0]=1;
for(int i=1; i<s.length(); i++)
for(int j=1; j<t.length(); j++)
if(s[i]==t[j]) dp[i][j]=dp[i-1][j-1]+dp[i-1][j];
else dp[i][j]=dp[i-1][j];
return (int)dp[s.length()-1][t.length()-1];
}
};