开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第24天,点击查看活动详情
115. 不同的子序列 题目描述:给定一个字符串 s 和一个字符串 t ,计算在 s 的子序列中 t 出现的个数。字符串的一个 子序列 是指,通过删除一些(也可以不删除)字符且不干扰剩余字符相对位置所组成的新字符串。(例如,"ACE" 是 "ABCDE" 的一个子序列,而 "AEC" 不是)
| 示例1 | 示例2 |
|---|---|
输入:s = "rabbbit", t = "rabbit" 输出:3 | 输入: s = "babgbag", t = "bag" 输出: 5 |
中规中矩的动态规划
昨天更新过一篇关于编辑距离入门的题解 # 392. 判断子序列,本题目同样仅涉及删除操作,不涉及替换、增加等操作。
1、确定 dp 状态数组
定义 是字符串 s 以 结尾子序中出现字符串 t 以 结尾子序的个数,其中,,。
2、确定 dp 状态方程
当 时,此时 只有一种情况:
- 放弃 ,;
当 时,此时 有两种情况需要考虑:
-
放弃 ,;
-
使用 ,;
此时
3、确定 dp 初始状态
为方便计算,我们在 数组多申请了一行一列的元素作为哨兵节点,即 与 。
-
代表了以 结尾的
s子序出现的 空字符串 的个数,即 ;(其中, 从 到 ) -
代表了空字符串中出现的以 结尾的
t子序的个数,即 。(其中, 从 到 )
4、确定遍历顺序
-
外层循环遍历字符串
s,从 到 ; -
内层循环遍历字符串
t,从 到 。
5、确定最终返回值
回归到状态定义中,即
6、代码示例
/**
* 空间复杂度 O(s.length * t.length)
* 时间复杂度 O(s.length * t.length)
*/
function numDistinct(s: string, t: string): number {
const lenS = s.length;
const lenT = t.length;
const dp = Array.from({ length: lenS + 1 }, () => new Array(lenT + 1).fill(0));
for (let i = 0; i <= lenS; i++) {
dp[i][0] = 1;
}
// 其实这里无须初始化,但是为了结构完整...
for (let j = 1; j <= lenT; j++) {
dp[0][j] = 0;
}
for (let i = 1; i <= lenS; i++) {
for (let j = 1; j <= lenT; 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[lenS][lenT];
};