不同的好子序列数目

标签: 字符串 动态规划

难度: Hard

给你一个二进制字符串 binary 。 binary 的一个 子序列 如果是 非空 的且没有 前导 0 (除非数字是 "0" 本身),那么它就是一个  的子序列。

请你找到 binary 不同好子序列 的数目。

  • 比方说,如果 binary = "001" ,那么所有  子序列为 ["0", "0", "1"] ,所以 不同 的好子序列为 "0" 和 "1" 。 注意,子序列 "00" ,"01" 和 "001" 不是好的,因为它们有前导 0 。

请你返回 binary 中 不同好子序列 的数目。由于答案可能很大,请将它对 109 + 7 取余 后返回。

一个 子序列 指的是从原数组中删除若干个(可以一个也不删除)元素后,不改变剩余元素顺序得到的序列。

示例 1:

输入:binary = "001"
输出:2
解释:好的二进制子序列为 ["0", "0", "1"] 。
不同的好子序列为 "0" 和 "1" 。

示例 2:

输入:binary = "11"
输出:2
解释:好的二进制子序列为 ["1", "1", "11"] 。
不同的好子序列为 "1" 和 "11" 。

示例 3:

输入:binary = "101"
输出:5
解释:好的二进制子序列为 ["1", "0", "1", "10", "11", "101"] 。
不同的好子序列为 "0" ,"1" ,"10" ,"11" 和 "101" 。

提示:

  • 1 <= binary.length <= 105
  • binary 只含有 '0' 和 '1'

Submission

运行时间: 84 ms

内存: 17.0 MB

class Solution:
    def numberOfUniqueGoodSubsequences(self, binary: str) -> int:
        mod = 10**9 + 7

        even = odd = 0
        for ch in binary:
            if ch == "0":
                even = (even + odd) % mod
            else:
                odd = (even + odd + 1) % mod
        
        ans = (even + odd + ("0" in binary)) % mod
        return ans

Explain

题解采用动态规划的思想,通过遍历字符串来构建所有可能的好子序列。定义两个变量even和odd,分别代表以'0'结尾和以'1'结尾的好子序列数量。遍历字符串的每个字符,根据当前字符是'0'还是'1',更新even或odd的值。最终结果为even和odd的和,再加上是否含有字符'0'(作为单独的子序列'0')。整个过程中,每个字符只更新even或odd,因此时间复杂度为O(n),空间复杂度为O(1)。

时间复杂度: O(n)

空间复杂度: O(1)

class Solution:
    def numberOfUniqueGoodSubsequences(self, binary: str) -> int:
        mod = 10**9 + 7  # 定义模数

        even = odd = 0  # 初始化以'0'和'1'结尾的好子序列数量
        for ch in binary:  # 遍历输入的二进制字符串
            if ch == "0":
                even = (even + odd) % mod  # 如果当前字符是'0',更新even
            else:
                odd = (even + odd + 1) % mod  # 如果当前字符是'1',更新odd
        
        ans = (even + odd + ("0" in binary)) % mod  # 计算总的好子序列数量,加上单独的'0'序列
        return ans

Explore

`even`和`odd`是动态规划中用于记录子序列的变量,其中`even`代表所有以'0'结尾的好子序列的数量,而`odd`代表所有以'1'结尾的好子序列的数量。选择这样的表示方法是为了在遍历字符串时能够有效地根据当前字符更新子序列的数量,同时确保算法的时间复杂度为O(n)。这种表示方法可以清晰地区分处理两种不同结尾字符的子序列,并简化状态转移的逻辑。

在处理字符'1'时,`odd`的更新表达式中额外加1是因为除了从现有的所有好子序列(以'0'和'1'结尾的)中添加'1'之外,还可以形成一个新的单字符子序列'1'。这个单独的'1'是一个有效的好子序列,所以在计算以'1'结尾的子序列数量时需要将它包括进去。

在这段代码中,我们并没有直接在算法中排除包含前导0的子序列,而是通过最终的结果计算方式间接处理了这个问题。即使`even`变量在遇到'0'时被更新,但最终结果在返回之前加上了单独的'0'子序列。这意味着即使在动态规划过程中可能计算了一些非法的带前导0的子序列,最终输出的结果仍然是正确的,因为我们只计算了合法的子序列数量。

题解中加上单独的'0'序列是因为在遍历字符串并动态更新`even`和`odd`时,我们考虑了由多个字符组成的子序列,但没有直接考虑单个字符'0'作为子序列的情况。在二进制字符串中,单独的'0'也是一个有效的子序列。因此,如果原始字符串中包含字符'0',我们需要在最终的子序列数量中额外加上这个单独的'0'子序列,以确保所有可能的好子序列都被计算在内。