Leetcode 115 不同的子序列

看到leetcode一个挺有趣的题目,总结如下,供参考
问题描述:

1 <= s.length, t.length <= 1000

s 和 t 由英文字母组成

解决在字符串 s 中查找 t 的子序列出现次数的三种方法

在这个问题中,我们需要统计字符串 s 中包含字符串 t 作为子序列的个数。这里我们将介绍三种不同的方法来解决这个问题,并详细解释每种方法的思路和实现。

方法一:动态规划

思路

使用动态规划来记录 s 的前 i 个字符中包含 t 的前 j 个字符作为子序列的个数。定义一个二维数组 dp[i][j],其中 dp[i][j] 表示 s 的前 i 个字符中包含 t 的前 j 个字符作为子序列的个数。

状态转移方程

  • 如果 s[i-1] == t[j-1],则 dp[i][j] = dp[i-1][j-1] + dp[i-1][j](选择当前字符或不选择)
  • 如果 s[i-1] != t[j-1],则 dp[i][j] = dp[i-1][j](不选择当前字符)

初始化

  • dp[0][0] = 1(两个空字符串是对方的子序列)
  • dp[i][0] = 1(空字符串 t 是任何字符串 s 的子序列)

实现

python 复制代码
def numDistinct(s: str, t: str) -> int:
    MOD = 10**9 + 7
    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):
            if s[i - 1] == t[j - 1]:
                dp[i][j] = (dp[i - 1][j - 1] + dp[i - 1][j]) % MOD
            else:
                dp[i][j] = dp[i - 1][j]
    return dp[m][n]
方法二:递归加记忆化

思路:

递归地考虑 s 的每个字符是否作为 t 的当前字符。为了避免重复计算,使用记忆化数组来存储已经计算过的结果。

实现:

python 复制代码
def numDistinctMemo(s: str, t: str) -> int:
    MOD = 10**9 + 7
    m, n = len(s), len(t)
    memo = [[-1] * (n + 1) for _ in range(m + 1)]

    def dfs(i, j):
        if j == 0:
            return 1
        if i == 0:
            return 0
        if memo[i][j] != -1:
            return memo[i][j]
        if s[i - 1] == t[j - 1]:
            memo[i][j] = (dfs(i - 1, j - 1) + dfs(i - 1, j)) % MOD
        else:
            memo[i][j] = dfs(i - 1, j)
        return memo[i][j]

    return dfs(m, n)
方法三:回溯(非最优解,仅作展示)

思路

直接通过回溯的方式枚举所有可能的子序列,并检查是否匹配 t。这种方法在数据量大时效率极低,但可作为理解问题的一种方式。

实现:

在这个示例中,我们将定义一个递归函数来尝试所有可能的子序列,并检查它们是否与 t 匹配。为了避免重复计算(尽管在这个简单的回溯实现中我们并没有显式地缓存结果,因为这将使代码变得复杂且仍然不是最优解),我们将直接搜索所有可能的子序列。

python 复制代码
def is_subsequence(s, t, i=0, j=0):
    """
    检查 s[i:] 是否是 t[j:] 的子序列
    """
    # 如果 t 已经遍历完,说明 s 是 t 的子序列
    if j == len(t):
        return True
    # 如果 s 已经遍历完,但 t 还没遍历完,则 s 不是 t 的子序列
    if i == len(s):
        return False
    # 如果当前字符匹配,则继续向后搜索
    if s[i] == t[j]:
        return is_subsequence(s, t, i + 1, j + 1)
    # 如果当前字符不匹配,则跳过 s 的当前字符,继续搜索
    return is_subsequence(s, t, i + 1, j)

def count_subsequences(s, t):
    count = 0
    # 对于 s 中的每个起始位置,尝试将其作为子序列的起始
    for start in range(len(s) - len(t) + 1):
        # 检查从 s[start:] 开始的子串是否是 t 的子序列
        if is_subsequence(s[start:], t):
            count += 1
    return count

# 示例
s = "rabbbit"
t = "rabbit"
print(count_subsequences(s, t))  # 输出: 3

s = "babgbag"
t = "bag"
print(count_subsequences(s, t))  # 输出: 5

然而,请注意,这个实现并不是最高效的。对于较长的字符串,它将执行大量的重复检查和递归调用。在实际应用中,更推荐使用动态规划或递归加记忆化的方法。

上面的 is_subsequence 函数检查从 s[i:] 开始的子串是否是 t[j:] 的子序列。count_subsequences 函数则遍历 s 中所有可能的起始位置,并调用 is_subsequence 来检查以该位置开始的子串是否是 t 的子序列。如果是,则计数器加一。

总结

在实际应用中,推荐使用动态规划或递归加记忆化的方法来解决这类问题,因为它们具有更高的效率。动态规划方法通过填表的方式避免了重复计算,而递归加记忆化方法则通过记忆化来优化递归过程中的重复计算,两者都是解决这类问题的有效手段。

相关推荐
binnnngo1 小时前
Minmax 算法与 Alpha-Beta 剪枝小教学
算法·机器学习·剪枝
এ᭄画画的北北3 小时前
力扣-287.寻找重复数
算法·leetcode
黑听人9 小时前
【力扣 困难 C】329. 矩阵中的最长递增路径
c语言·leetcode
YuTaoShao10 小时前
【LeetCode 热题 100】141. 环形链表——快慢指针
java·算法·leetcode·链表
小小小新人1212311 小时前
C语言 ATM (4)
c语言·开发语言·算法
你的冰西瓜12 小时前
C++排序算法全解析(加强版)
c++·算法·排序算法
এ᭄画画的北北12 小时前
力扣-31.下一个排列
算法·leetcode
绝无仅有13 小时前
企微审批对接错误与解决方案
后端·算法·架构
趣多多代言人14 小时前
从零开始手写嵌入式实时操作系统
开发语言·arm开发·单片机·嵌入式硬件·面试·职场和发展·嵌入式