问题背景
小U有一个字符串 s,他想计算该字符串的所有不同非空子序列的个数。子序列是通过删除原字符串中的部分字符(也可以不删除),且保持剩余字符的相对顺序形成的新字符串。
你的任务是帮助小U计算 s 的不同非空子序列的总数,并返回对 10^9 + 7 取余的结果。
例如:当 s = "abc" 时,所有不同的非空子序列包括 "a", "b", "c", "ab", "ac", "bc", 和 "abc",总数为 7。
测试样例
样例1:
输入:
s = "abc"
输出:7
样例2:
输入:
s = "aaa"
输出:3
样例3:
输入:
s = "abcd"
输出:15
样例4:
输入:
s = "abac"
输出:13
问题理解
我们需要计算字符串 s 的所有不同非空子序列的个数。子序列是通过删除原字符串中的部分字符(也可以不删除),且保持剩余字符的相对顺序形成的新字符串。
数据结构选择
我们可以使用动态规划(Dynamic Programming, DP)来解决这个问题。动态规划可以帮助我们高效地计算所有可能的子序列,并避免重复计算。
算法步骤
-
定义状态:
- 设
dp[i]表示前i个字符可以形成的不同子序列的个数。 - 初始状态:
dp[0] = 1,表示空字符串也算一个子序列。
- 设
-
状态转移:
-
对于每个字符
s[i],我们需要考虑两种情况:- 不包含
s[i]的子序列,这部分的数量是dp[i-1]。 - 包含
s[i]的子序列,这部分的数量是dp[i-1]加上所有以s[i]结尾的子序列。
- 不包含
-
为了避免重复计算,我们可以使用一个哈希表
last_seen来记录每个字符最后一次出现的位置。
-
-
最终结果:
- 最终结果是
dp[n] - 1,其中n是字符串的长度,减去 1 是因为要去掉空字符串的情况。
- 最终结果是
def solution(s: str) -> int:
MOD = 10**9 + 7
n = len(s)
# 初始化 dp 数组
dp = [0] * (n + 1)
dp[0] = 1 # 空字符串也算一个子序列
# 记录每个字符最后一次出现的位置
last_seen = {}
for i in range(1, n + 1):
dp[i] = dp[i - 1] * 2 % MOD
# 如果当前字符之前出现过,减去重复的部分
if s[i - 1] in last_seen:
dp[i] = (dp[i] - dp[last_seen[s[i - 1]] - 1] + MOD) % MOD
# 更新当前字符的最后出现位置
last_seen[s[i - 1]] = i
# 返回结果,减去空字符串的情况
return (dp[n] - 1 + MOD) % MOD
关键步骤解释
dp[i] = dp[i - 1] * 2 % MOD:表示不包含当前字符和包含当前字符的子序列总数。if s[i - 1] in last_seen:检查当前字符是否之前出现过,如果出现过,则需要减去重复的部分。last_seen[s[i - 1]] = i:更新当前字符的最后出现位置。