📜  计算不同的子序列(1)

📅  最后修改于: 2023-12-03 14:57:27.098000             🧑  作者: Mango

计算不同的子序列

有时候我们需要计算一个字符串中不同的子序列的数量。子序列指的是从原字符串中删除若干个字符后得到的结果。

举个例子,字符串 abc 的子序列有 a,b,c,ab,ac,bc,abc 共 7 种。

下面介绍两种计算不同子序列数量的方法。

动态规划

动态规划是一种求解优化问题的方法。对于计算不同子序列数量,可以采用动态规划的思想。

定义 dp[i][j] 表示字符串 s[:i](即前 i 个字符)和 t[:j](即前 j 个字符)的不同子序列数量。

考虑 s[i]t[j] 是否匹配:

  • 如果 s[i] != t[j],此时 s[i]t[j] 不能同时包含在任何一个子序列中,因此 dp[i][j] 只由 s[:i-1]t[:j-1] 贡献。
  • 如果 s[i] == t[j],则子序列可以包含 s[i](也可以不包含),此时 s[i-1]t[j-1] 也可以在子序列中,此时 dp[i][j] 取决于 s[:i-1]t[:j] 以及 s[:i]t[:j-1]

最终的不同子序列数量为 dp[m][n],其中 mn 分别为字符串 st 的长度。

具体实现参考代码如下(Python3):

def numDistinct(s: str, t: str) -> int:
    m, n = len(s), len(t)
    dp = [[0] * (n + 1) for _ in range(m + 1)]
    for i in range(m + 1):
        dp[i][0] = 1
    for i in range(1, m + 1):
        for j in range(1, n + 1):
            dp[i][j] = dp[i-1][j]
            if s[i-1] == t[j-1]:
                dp[i][j] += dp[i-1][j-1]
    return dp[m][n]
递归

递归计算不同子序列数量的思路如下:

  • 如果两个字符串均为空,则返回 1。
  • 如果其中一个字符串为空,另一个字符串不为空,则返回 0。
  • 如果两个字符串的最后一个字符相同,则递归计算两个字符串的子串的不同子序列数量之和。
  • 如果两个字符串的最后一个字符不同,则递归计算 s 去掉最后一个字符和 t 的不同子序列数量。

具体实现参考代码如下(Python3):

def numDistinct(s: str, t: str) -> int:
    if not t:
        return 1
    if not s:
        return 0
    if s[-1] == t[-1]:
        return numDistinct(s[:-1], t[:-1]) + numDistinct(s[:-1], t)
    else:
        return numDistinct(s[:-1], t)

以上两种方法的时间复杂度均为 $O(mn)$,其中 $m$ 和 $n$ 分别为字符串 st 的长度。递归方法的空间复杂度为 $O(mn)$,动态规划方法的空间复杂度为 $O(mn)$ 或 $O(n)$。