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 的子序列)

实现

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 的当前字符。为了避免重复计算,使用记忆化数组来存储已经计算过的结果。

实现:

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 匹配。为了避免重复计算(尽管在这个简单的回溯实现中我们并没有显式地缓存结果,因为这将使代码变得复杂且仍然不是最优解),我们将直接搜索所有可能的子序列。

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

总结

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

### LeetCode 最长公共子序列问题的解决方案 解决LeetCode上的最长公共子序列(LCS)问题通常采用动态规划方法。以下是详细的解答过程以及Python实现。 #### 动态规划的核心思路 定义一个二维数组 `dp`,其中 `dp[i][j]` 表示字符串 `text1[:i]` 和 `text2[:j]` 的最长公共子序列长度。状态转移方程可以表示为: - 如果 `text1[i-1] == text2[j-1]`,则有: \[ dp[i][j] = dp[i-1][j-1] + 1 \] - 否则,取两者中的最大值: \[ dp[i][j] = \max(dp[i-1][j], dp[i][j-1]) \] 初始条件为当任意一方为空串时,其公共子序列为零,即 `dp[0][j] = 0` 和 `dp[i][0] = 0` 对于所有的 \( i, j \geq 0 \)[^1]。 #### Python 实现代码 下面是基于上述逻辑的Python实现: ```python def longestCommonSubsequence(text1: str, text2: str) -> int: m, n = len(text1), len(text2) # 创建DP表并初始化 dp = [[0] * (n + 1) for _ in range(m + 1)] # 填充DP表 for i in range(1, m + 1): for j in range(1, n + 1): if text1[i - 1] == text2[j - 1]: dp[i][j] = dp[i - 1][j - 1] + 1 else: dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]) return dp[m][n] ``` 此函数接受两个字符串作为输入参数,并返回它们之间的最长公共子序列的长度[^3]。 #### 示例运行 考虑以下测试用例及其解释: - 输入 `text1 = "abcde"` 和 `text2 = "ace"`,输出应为 `3`,因为最长公共子序列是 `"ace"`[^2]。 - 当两字符串完全相同时,例如 `text1 = "abc"` 和 `text2 = "abc"`,结果亦为其长度 `3`。 - 若无任何共同字符存在,则如例子三所示,返回值应当为 `0`[^4]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

科技之歌

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值