力扣挑战赛第3天-No.115不同的子序列

题目描述

给定一个字符串 s 和一个字符串 t ,计算在 s 的子序列中 t 出现的个数。

字符串的一个 子序列 是指,通过删除一些(也可以不删除)字符且不干扰剩余字符相对位置所组成的新字符串。(例如,”ACE” 是 “ABCDE” 的一个子序列,而 “AEC” 不是)

题目数据保证答案符合 32 位带符号整数范围。

示例:

输入:s = "babgbag", t = "bag"
输出:5
解释:
如下所示, 有 5 种可以从 s 中得到 "bag" 的方案。 
(上箭头符号 ^ 表示选取的字母)
babgbag
^^ ^
babgbag
^^    ^
babgbag
^    ^^
babgbag
  ^  ^^
babgbag
    ^^^

注意:

0 <= s.length, t.length <= 1000
s 和 t 由英文字母组成

解法

动态规划:

假设 m = s.length,n = t.length,
创建二维数组 dp,dp[i][j] 表示在 s[i:] 的子序列中 t[j:] 出现的个数,
s[i:] 表示 s 从下标 i 到末尾的子字符串,t[j:] 表示 t 从下标 j 到末尾的子字符串,则有如下推断:

  • 当 m < n 时,t 一定不是 s 的子序列;
  • 当 j = n 时,t[j:] 为空字符串,因此对任意 0 <= i <= m,有 d[i][n] = 1;
  • 当 i = m 时,s[i:] 为空字符串,因此对任意 0 <= j < n,dp[m][j] = 0;
  • 当 i < m 且 j < n 时:
    1. 如果 s[i] === s[j],dp[i][j] 由两部分组成,选择 s[i],则这部分的子序列数为 dp[i + 1][j + 1],不选择 s[i],则这部分的子序列数为 dp[i + 1][j],因此,dp[i][j] = dp[i + 1][j + 1] + dp[i + 1][j];
    2. 如果 s[i] !== s[j],则 dp[i][j] = dp[i + 1][j];

由此可得状态转移方程:

dp[i][j] = dp[i + 1][j + 1] + dp[i + 1][j]  s[i] === s[j]
         = dp[i + 1][j]                     s[i] !== s[j]

最终 dp[0][0] 即为在 s 的子序列中 t 出现的个数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
* @param {string} s
* @param {string} t
* @return {number}
*/
const numDistinct = function (s, t) {
const m = s.length;
const n = t.length;
if (m < n) {
return 0;
}
const dp = new Array(m + 1).fill(0).map(() => new Array(n + 1).fill(0));
for (let i = 0; i <= m; i++) {
dp[i][n] = 1;
}
for (let i = m - 1; i >= 0; i--) {
for (let j = n - 1; j >= 0; 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 dp[0][0];
};
Donate
  • Copyright: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.
  • Copyrights © 2020-2021 Sanmu
  • Visitors: | Views:

请我喝杯咖啡吧~

支付宝
微信