LeetCode探索(150):940-不同的子序列 II

196 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第15天,点击查看活动详情

题目

给定一个字符串 s,计算 s不同非空子序列 的个数。因为结果可能很大,所以返回答案需要对 10^9 + 7 取余

字符串的 子序列 是经由原字符串删除一些(也可能不删除)字符但不改变剩余字符相对位置的一个新字符串。

  • 例如,"ace""abcde" 的一个子序列,但 "aec" 不是。

示例 1:

输入:s = "abc"
输出:7
解释:7 个不同的子序列分别是 "a", "b", "c", "ab", "ac", "bc", 以及 "abc"

示例 2:

输入:s = "aba"
输出:6
解释:6 个不同的子序列分别是 "a", "b", "ab", "ba", "aa" 以及 "aba"

示例 3:

输入:s = "aaa"
输出:3
解释:3 个不同的子序列分别是 "a", "aa" 以及 "aaa"

提示:

  • 1 <= s.length <= 2000
  • s 仅由小写英文字母组成

思考

本题难度困难。

首先是读懂题意。给定一个字符串 s,计算 s 的 不同非空子序列 的个数。字符串的子序列是经由原字符串删除一些(也可能不删除)字符但不改变剩余字符相对位置的一个新字符串。

我们可以使用动态规划的方法解题。使用一个长度为 |Σ|=26 的数组 g 来进行动态规划,数组元素初始化为 0。

遍历字符串 s 的每个字符,用 total 记录以该字符结尾的子序列的出现次数,初始值为1,因为新出现的单个字符的次数是1。

遍历数组 g,如果 g[j] 之前出现过,则将次数累加起来。接着,更新该字符的出现次数为出现次数。

最后,遍历所有小写字母,将次数累加起来即可。

解答

方法一:动态规划

/**
 * @param {string} s
 * @return {number}
 */
var distinctSubseqII = function(s) {
  const MOD = 1000000007
  const g = new Array(26).fill(0)
  for (let i = 0; i < s.length; ++i) {
    let total = 1
    for (let j = 0; j < 26; ++j) {
      total = (total + g[j]) % MOD
    }
    g[s[i].charCodeAt() - 'a'.charCodeAt()] = total
  }
  let ans = 0
  for (let i = 0; i < 26; ++i) {
    ans = (ans + g[i]) % MOD
  }
  return ans
}
// 执行用时:64 ms, 在所有 JavaScript 提交中击败了80.00%的用户
// 内存消耗:41.4 MB, 在所有 JavaScript 提交中击败了86.67%的用户
// 通过测试用例:109 / 109

复杂度分析:

  • 时间复杂度:O(n|Σ|),其中 n 是字符串 s 的长度,Σ 是字符集,|Σ|=26。
  • 空间复杂度:O(n + |Σ|)。即为数组 f 和 last 需要的空间。

参考