题目描述
给定字符串 $S$ 和 $T$,统计 $S$ 中有多少个不同的子序列和 $T$ 相等。
字符串的子序列是指:将原字符串删掉若干字符后,其余字符相对顺序不改变,所得到的新串(例如:"ACE"
是"ABCDE"
的子序列,而"AEC"
不是)。
样例1
输入:S = "rabbbit", T = "rabbit"
输出:3
解释:如下所示,共有3种方案。
'^' 表示选择的字符
rabbbit
^^^^ ^^
rabbbit
^^ ^^^^
rabbbit
^^^ ^^^
样例2
输入:S = "babgbag", T = "bag"
输出:5
解释:如下所示,共有5种方案。
'^' 表示选择的字符
babgbag
^^ ^
babgbag
^^ ^
babgbag
^ ^^
babgbag
^ ^^
babgbag
^^^
算法
(动态规划) $O(nm)$
可以换一种考虑问题的方式:用 $S$ 中的字符,按顺序匹配 $T$ 中的字符,问有多少种方式可以匹配完 $T$ 中的所有字符。
可以用动态规划来做:
$f[i][j]$ 表示用 $S$ 的前 $i$ 个字符,能匹配完 $T$ 的前 $j$ 个字符的方案数。
初始化:因为 $S$ 可以从任意一个字符开始匹配,所以 $f[i][0] = 1, \forall i \in [0, len(S)]$。
状态转移:
- 如果 $S[i - 1] \neq T[j - 1]$,则 $S[i-1]$ 不能匹配 $T[j-1]$,所以 $f[i][j] = f[i - 1][j]$;
- 如果 $S[i - 1] = T[j - 1]$,则 $S[i-1]$ 既可以匹配 $T[j-1]$,也可以不匹配 $T[j - 1]$,所以 $f[i][j] = f[i - 1][j] + f[i - 1][j - 1]$;
时间复杂度分析:假设 $S$ 的长度是 $n$,$T$ 的长度是 $m$,则共有 $nm$ 个状态,状态转移的复杂度是 $O(1)$,所以总时间复杂度是 $O(nm)$。
C++ 代码
class Solution {
public:
int numDistinct(string s, string t) {
int n = s.size(), m = t.size();
vector<vector<long long>> f(n + 1, vector<long long>(m + 1));
for (int i = 0; i <= n; i ++ ) f[i][0] = 1;
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ )
{
f[i][j] = f[i - 1][j];
if (s[i - 1] == t[j - 1])
f[i][j] += f[i - 1][j - 1];
}
return f[n][m];
}
};
2024年7月23日 题目已经改为对 1e9+7取模了
使用unsigned自动取模
unsigned long long 其实也不保证能过。尽管答案的值有限制,但是s和t的子数组的f[i][j]不一定有限制。
我的建议是f[i][j]加上一个数时,都做一个取模操作(比如取模1e16),保证子数组的dp[i][j]不会溢出,并且不会影响最终答案(因为最终答案的值远小于1e6)。
得用unsigned long long 才行,。。。我还debug半天,看了y总的代码和我一样,力扣太坑了。
复议
得用unsigned long long 才行,。。。我还debug半天,看了y总的代码和我一样,力扣太坑了。
lc用unsigned long long 定义才过的
好像又不能过了
确实,数据又加强了
好像要用unsigned才过得了
是滴
力扣不讲武德啊, 说的不需要long long 的
我也被坑了…不过它这个f数组并不是递增的,有可能答案符合int范围,但是中间结果爆int。
菜鸡求问大佬,为什么比较的是S[i−1]≠T[j−1] ,而不是s[j] ≠T[j]
因为 f 数组的下标是从1开始的,表示前 i 个字母和前 j 个字母,而在 S[] 中第 i 个字母的下标是 S[i - 1],T[] 中第 j 个字母的下标是 T[j - 1]
这个题leetcode更新了边界问题,原代码已经不能AC了,定义vector[HTML_REMOVED] 就可以了。
long long
代码已改~ 多谢hh