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 的子序列。如果是,则计数器加一。

总结

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

相关推荐
浅念同学1 小时前
算法-常见数据结构设计
java·数据结构·算法
UndefindX1 小时前
PAT甲级1006 :Sign In and Sign Out
数据结构·算法
T风呤4 小时前
学生管理系统(通过顺序表,获取连续堆区空间实现)
算法
stackY、5 小时前
【Linux】:程序地址空间
linux·算法
心死翼未伤5 小时前
【MySQL基础篇】多表查询
android·数据结构·数据库·mysql·算法
Orion嵌入式随想录6 小时前
算法训练 | 图论Part1 | 98.所有可达路径
算法·深度优先·图论
西西,正在减肥6 小时前
【leetcode52-55图论、56-63回溯】
算法
Beast Cheng6 小时前
07-7.1.1 查找的基本概念
数据结构·笔记·考研·算法·学习方法
DogDaoDao6 小时前
LeetCode 算法:二叉树中的最大路径和 c++
c++·算法·leetcode·二叉树·二叉树路径
望舒_2336 小时前
【算法专题】双指针算法
算法