看到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
的子序列。如果是,则计数器加一。
总结
在实际应用中,推荐使用动态规划或递归加记忆化的方法来解决这类问题,因为它们具有更高的效率。动态规划方法通过填表的方式避免了重复计算,而递归加记忆化方法则通过记忆化来优化递归过程中的重复计算,两者都是解决这类问题的有效手段。