Skip to main content

不同的子序列

题目

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

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

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

提示:
  • 0 <= s.length, t.length <= 1000
  • st 由英文字母组成
示例

输入: s = "babgbag", t = "bag"

输出: 5

解释: 有 5 种可以从 s 中得到 "bag" 的方案.

  • babgbag
  • babgbag
  • babgbag
  • babgbag
  • babgbag

题解

自上而下的递归搜索过程都可以修改为复杂度更低的自下而上的动态规划过程, 下面介绍一下动态规划的实现方法.

ms.length, nt.length, 创建二维数组 dp(m + 1)·(n + 1), dp[i][j] 表示 t 的前 j 个连续字符(即 t[:j])在 s 的前 i 个字符(即s[:i])出现的个数.

  • 初始条件: dp[0][0] = 0
  • 边界条件: 当 j0 时, t[:j] 就是空字符串, 那么如果让 s[:i] 变成空字符串, 那只能把 s 的前 i 个字符全部删掉.
  • 状态转移方程:
    • t[j - 1] !== s[i - 1], 相当于在 s 中新加入的第 i 个字符 s[i - 1] 没起到作用, 那么 dp[i][j] = dp[i - 1][j]
    • 否则, dp[i][j] 可以有两部分组成:
      • 一部分是用 s[i - 1] 来匹配, 那么个数为 dp[i - 1][j - 1]
      • 一部分是不用 s[i - 1] 来匹配, 个数为 dp[i - 1][j]

这块当时没搞懂, 既然 t[j - 1] === s[i - 1], 为什么还要考虑不用 s[i - 1] 来匹配? 举个例子, sbagg, tbag, s[3]t[2] 是相同的, 因此可以用 s[3] 来匹配, 即 s[0], s[1], s[3] 组成的 bag; 但是字符串 s 也可以不用 s[3] 来匹配, (比如使用 s[0], s[1], s[2] 组成的 bag).

因此对于 t[j - 1] === s[i - 1] 的状态转移方程为: dp[i][j] = dp[i - 1][j] + dp[i - 1][j - 1].

用二维数组表示如下:

115-num-distinct

下图以 t = bag, s = babgbag 为例, 展示了这一流程:

115-num-distinct

/**
* @param {string} s
* @param {string} t
* @return {number}
*/
var numDistinct = function (s, t) {
const m = s.length
const n = t.length
if (n > m) 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][0] = 1
}

for (let i = 1; i <= m; i++) {
for (let j = 1; j <= n; j++) {
if (s[i - 1] !== t[j - 1]) {
dp[i][j] = dp[i - 1][j]
} else {
dp[i][j] = dp[i - 1][j] + dp[i - 1][j - 1]
}
}
}

return dp[m][n]
}