<think>首先,用户的问题是:“我想计算一个字符串中不同子序列的数量 请问如何计算一个字符串中不同的子序列的个数”。所以,我需要解释如何计算一个字符串中所有不同的子序列的数量。
关键点是“不同的子序列”,意思是子序列是唯一的,不考虑重复。例如,字符串 "aba" 的子序列包括 ""、"a"、"b"、"a"(但有两个 'a',所以如果只考虑唯一,那么 "a" 只算一次)、"ab"、"aa"、"ba"、"aba"。但 "a" 出现两次,但作为序列,位置不同,但值相同,所以不同子序列指的是序列内容不同,而不是位置不同。
子序列是从原字符串中删除一些字符(可能不删除)而不改变剩余字符相对顺序形成的序列。所以,对于 "aba",子序列有:
- 空序列: ""
- 单个字符: "a"(位置1)、"b"(位置2)、"a"(位置3)
- 两个字符: "ab"(位置1和2)、"aa"(位置1和3)、"ba"(位置2和3)
- 三个字符: "aba"(所有位置)
但当我们说“不同”的子序列时,我们关心序列的值,而不是位置。所以 "a"(位置1)和 "a"(位置3)都表示序列 "a",因此只算一个不同的子序列 "a"。类似地,"aa" 只有一个。
所以,对于 "aba",不同的子序列有: "", "a", "b", "ab", "aa", "ba", "aba"。总共7个。
现在,问题是如何计算这个数量。
我需要参考用户提供的引用。用户给出了几个引用:
- 引用[1]:关于动态规划计算子序列出现次数,但那是针对另一个字符串在子序列中出现的次数,不是计算所有不同子序列的数量。
- 引用[2]:提到如果不需要打印所有字面值不重复的子序列,而只需要知道个数,可以用动态规划。
- 引用[3]:关于C语言中查找子字符串的个数,但子字符串是连续的,子序列不连续。
- 引用[4]:LeetCode问题115,计算s的子序列中t出现的个数,又是指定子序列的出现次数。
用户的问题是关于计算一个字符串中所有不同子序列的数量,而不是指定子序列的出现次数。
所以,我需要专注于计算所有不同子序列的数量。
一个常见的方法是使用动态规划(DP),因为子序列的数量可以递归地定义。
让我回忆一下DP方法。
设 dp[i] 表示字符串 s 的前 i 个字符中不同子序列的数量。
但我们需要考虑字符的重复,以避免重复计数。
一个标准的方法是使用一个数组来跟踪每个位置的不同子序列数量,同时考虑字符的最后一个出现位置以避免重复。
定义:
让 dp[i] 表示以 s 的前 i 个字符结尾的不同子序列的数量(包括空序列)。
但更精确地,dp[i] 通常定义为考虑前 i 个字符时,所有可能的子序列数量。
但为了避免重复,当添加一个新字符时,如果该字符之前出现过,我们需要减去由于重复而导致的额外计数。
设 s 是字符串,索引从 1 开始。
定义 dp[i] 为考虑 s[1..i] 时,不同子序列的数量(包括空序列)。
初始化:dp[0] = 1,对应空序列。
对于每个位置 i 从 1 到 n:
- 当我们添加字符 s[i],新的子序列可以通过将 s[i] 附加到所有现有子序列的末尾来形成,或者不添加,但 dp[i] 应该包括所有可能。
更仔细地:
当我们处理到位置 i 时,子序列包括:
- 所有不包含 s[i] 的子序列:这些就是 dp[i-1]
- 所有包含 s[i] 的子序列:这些可以通过将 s[i] 附加到每个以 s[i] 结尾或不以 s[i] 结尾的子序列,但我们需要确保不重复。
标准方法是:
dp[i] = 2 * dp[i-1]
但这样会重复计数,因为如果 s[i] 之前出现过,一些子序列会被重复添加。
例如,s = "ab":
dp[0] = 1 (空序列)
处理 s[1] = 'a':新子序列: "" + "a" = "a",所以 dp[1] = 2: "", "a"
处理 s[2] = 'b':新子序列:将 'b' 附加到 "" 和 "a",得到 "b" 和 "ab",所以 dp[2] = 4: "", "a", "b", "ab"
现在 s = "aa":
dp[0] = 1
处理 s[1] = 'a':dp[1] = 2: "", "a"
处理 s[2] = 'a':如果直接 dp[2] = 2 * dp[1] = 4,但实际子序列: "", "a" (第一个), "a" (第二个), "aa"
但 "a" 出现了两次,所以如果只考虑不同,应为 "", "a", "aa" — 3个,而不是4个。
所以,当 s[i] 是重复字符时,我们需要减去由于上一次出现而导致的额外计数。
具体地,当添加 s[i],如果 s[i] 之前出现过,设 last[s[i]] 是 s[i] 上一次出现的索引,那么所有以 last[s[i]] 结尾的子序列在添加 s[i] 时会重复。
更一般地:
设 dp[i] 表示考虑前 i 个字符的不同子序列数量。
dp[i] = 2 * dp[i-1] - dp[k-1] 其中 k 是 s[i] 上一次出现的位置,如果 s[i] 之前没出现过,则减去0。
在 "aa" 例子:
i=1, s[1]='a', 之前没出现,dp[1] = 2 * dp[0] - 0 = 2*1 -0 =2
i=2, s[2]='a', last['a'] =1, k=1, dp[k-1] = dp[0] =1
所以 dp[2] = 2 * dp[1] - dp[0] = 2*2 -1 = 4-1=3,正确: "", "a", "aa"
另一个例子 s="aba"
首先,索引:s[1]='a', s[2]='b', s[3]='a'
dp[0] =1
i=1, s[1]='a', 第一次出现,dp[1] = 2*1 -0 =2: "", "a"
last['a'] =1
i=2, s[2]='b', 第一次出现,dp[2] = 2* dp[1] -0 =4: "", "a", "b", "ab"
last['b']=2
i=3, s[3]='a', last['a']=1, k=1, dp[k-1] = dp[0] =1
dp[3] = 2 * dp[2] - dp[0] = 2*4 -1 =8-1=7
现在,实际不同子序列: "", "a", "b", "ab", "aa", "ba", "aba" — 是的,7个。
完美。
所以一般算法:
- 初始化 dp[0] = 1(空序列)
- 创建一个字典或数组来存储每个字符上一次出现的索引,初始为 -1 或 0 表示未出现。
- 对于 i 从 1 到 n(n 是字符串长度):
- dp[i] = 2 * dp[i-1]
- 如果 last_occurrence[s[i]] 存在,设 k = last_occurrence[s[i]],则 dp[i] -= dp[k-1] ?等一下,索引问题。
在公式中,当减去时,是减去 dp[k-1],其中 k 是上一次出现的索引。
在代码中,索引通常从 0 开始。
设 s 索引从 0。
定义 dp[i] 为考虑 s[0..i-1] 或 s[0..i]?需要一致。
通常,让 dp[i] 表示考虑前 i 个字符,即 s[0] 到 s[i-1]。
但为了简单,在 DP 中,我们使用基于 1 的索引,但代码中用基于 0。
在实现时:
设 n = len(s)
dp[0] = 1 # 空序列
last_occurrence = {} # 字符到索引的映射,存储上一次出现的索引
对于 i 从 0 到 n-1:
char = s[i]
# 计算 dp[i+1],因为 dp[i] 对应前 i 个字符
设 dp[i] 表示处理到索引 i(包括 i)的子序列数量。
更好:设 dp[i] 是考虑子字符串 s[0..i] 的不同子序列数量。
初始化:当 i = -1,空前缀,dp[-1] 不实用。
所以,定义 dp[i] 为考虑字符 s[0] 到 s[i] 的不同子序列数量。
dp[0] = 2: 空序列和 s[0] 本身。但空序列是当没有字符时。
标准:dp[i] 包括所有使用 s[0] 到 s[i] 中字符的子序列,包括空序列。
对于 i=0:只有空序列和 s[0],所以 2 个。
但空序列是通用的。
在代码中:
n = len(s)
dp = [0]*(n+1)
dp[0] = 1 # 空序列,对于空前缀
last_occurrence = {} # 存储字符最后一次出现的索引,初始为 -1
对于 i 从 0 到 n-1:
char = s[i]
# dp[i+1] 对应 s[0..i]
dp[i+1] = 2 * dp[i] # 添加 s[i] 到所有现有子序列,或保持原样
# 但如果 char 之前出现过,需要减去重复部分
if char in last_occurrence:
last_index = last_occurrence[char] # 上次出现的索引
# 当 char 上次出现时,所有以该位置结尾的子序列在添加当前 char 时会被重复
# 减去的是 dp[last_index],但等等
在公式中,我们减去 dp[k-1],其中 k 是上次出现的索引。
在基于 0 的索引中:
设 k = last_occurrence[char] # 索引,比如 j
然后,我们需要减去 dp[j],但 dp[j] 对应 s[0..j-1]?混乱。
回忆:当我们添加 s[i],如果 s[i] 上次出现在位置 j,那么所有在 j 之前形成的子序列,在 j 处添加 s[i] 时已经创建了以 s[i] 结尾的序列,现在在 i 处添加 s[i] 会再次创建相同的序列,但只在当序列以 j 之后开始,但等等。
更好的想法:当我们添加 s[i],新子序列是:所有旧子序列,加上所有旧子序列 + s[i]。
但如果有重复字符,一些序列会重复。
具体地,序列以 s[i] 结尾的,但值相同。
在位置 j(上次出现)时,我们添加了所有以该字符结尾的子序列。
在位置 i,当我们添加 s[i],我们添加的新序列是所有以 s[i] 结尾的子序列,但其中一些在位置 j 时已经存在。
所以,重复的数量是位置 j-1 时所有可能的子序列数量,因为那些是当我们在位置 j 添加字符时附加的序列。
在位置 j,当我们添加 s[j],新添加的序列是所有以 s[j] 结尾的序列,这些序列的数量等于在 j 之前的所有子序列数量,即 dp[j-1]。
类似地,在位置 i,新添加的序列数量应为 dp[i-1],但如果 s[i] == s[j],那么这些新序列中,有些在位置 j 时已经添加过了,所以我们需要减去在位置 j 添加时新序列的数量,即 dp[j-1]。
因此,dp[i] = 2 * dp[i-1] - dp[j-1],其中 j 是 s[i] 的上一次出现位置。
在基于 0 的索引中:
设 dp[i] 表示考虑前 i 个字符(s[0] 到 s[i-1])的不同子序列数量,但通常我们定义 dp[i] 为考虑前 i 个元素。
定义:设 dp[k] 为考虑字符串的前 k 个字符的不同子序列数量。
所以,对于 k=0:空字符串,dp[0] = 1(只有空序列)
对于 k=1:前 1 个字符,dp[1] = 2:空序列和序列 "s0"
通用情况:
对于每个位置 i(从 1 开始),当我们添加字符 c = s[i](索引 i 从 1 开始,s1 是第一个字符)
但 s 索引从 0。
设 s[0..n-1]
定义 dp[i] 为考虑子字符串 s[0..i-1] 的不同子序列数量,所以对于 i 个字符。
dp[0] = 1(空)
对于 i 从 1 到 n:
c = s[i-1] # 第 i 个字符
dp[i] = 2 * dp[i-1]
if c 在 s[0..i-2] 中出现过,设 j 是 c 在 i-1 之前最后一次出现的索引(0 到 i-2),那么设 last_index = j
但 j 是索引,我们需要 dp 的索引。
在公式中:如果 c 有上一次出现,设 k 为上一次出现的位置(索引),但 dp 是基于长度的。
设 last_occurrence 存储字符最后一次出现的起始索引。
在代码中:
n = len(s)
dp = [0] * (n+1)
dp[0] = 1 # 空序列
last_occurrence = {} # 字符: 最后出现的索引
for i in range(n): # i 从 0 到 n-1,处理 s[i]
c = s[i]
dp[i+1] = 2 * dp[i] # 默认情况,如果 c 是新的
if c in last_occurrence:
last_index = last_occurrence[c] # 最后出现的索引,比如 j
# 我们需要减去在位置 last_index 处添加时新创建的子序列数量
# 当 c 在位置 last_index 被添加时,新序列的数量是 dp[last_index](但 dp[last_index] 是到 last_index-1?)
定义:设 j 是 c 最后一次出现的索引。
当我们在位置 j 添加 c 时,新子序列的数量是 dp[j],因为 dp[j] 包含了所有新序列,但 dp[j] 是到索引 j 为止的总数。
在位置 j,添加 s[j] 后,新子序列的数量是 dp[j] - dp[j-1]?不对。
dp[j] 是考虑前 j 个字符的总数(s[0] 到 s[j-1]?索引问题。
最好使用一致的方式。
设 dp[i] 表示考虑子字符串 s[0..i-1] 的不同子序列数量,所以对于长度为 i 的前缀。
但当我们处理 s[i] 时,是第 (i+1) 个字符。
在循环中,对于每个索引 i(0 到 n-1),我们处理字符 s[i],并计算到该点为止的数量。
设 dp[i] 表示考虑 s[0..i] 的不同子序列数量(包括 s[i])。
所以对于 i 从 0 开始:
当 i = -1 时,dp[-1] 未定义。
所以,dp[0]:只考虑 s[0],子序列:"" 和 s[0],所以是 2 个。
然后对于 i=1,处理 s[1],等等。
在代码中:
n = len(s)
如果 n == 0,则返回 1(只有空序列)
dp = [0] * (n) # 但我们需要 dp[0] 表示 s[0],但空序列呢?
使用大小为 (n+1) 的 dp,其中 dp[i] 表示考虑 s[0..i-1] 的数量,所以对于 i 从 0 到 n,dp[0] 是空字符串。
对于 i 在 0 到 n-1 的范围内:处理 s[i],并设置 dp[i+1]
c = s[i]
dp[i+1] = 2 * dp[i]
如果 c 在 last_occurrence 中:
j = last_occurrence[c] # 最后出现的索引,例如位置 j
# 那么我们需要减去 dp[j],但 dp[j] 是到索引 j-1 为止?不是
dp[j] 是考虑 s[0..j-1] 的数量,但 j 是索引,所以对于位置 j,dp[j+1] 是考虑 s[0..j] 的数量。
设 last_index 为最后出现的索引。
当字符在位置 last_index 被添加时,新子序列的数量是 dp[last_index],但 dp[last_index] 是到 last_index-1?混乱。
定义:设 k 为最后出现的索引。
那么当我们在位置 k 添加字符时,新创建的子序列数量是 dp[k](如果 dp[k] 包含新增),但 dp[k] 是总数。
实际上,在位置 k 添加时,新子序列的数量等于在位置 k 添加之前存在的子序列数量,也就是 dp[k](如果 dp[k] 是添加前的数量)。
在序列中:
当处理 s[k] 时,在添加之前,子序列的数量是 dp[k](对于前缀 s[0..k-1]),然后添加 s[k] 后,我们添加了 dp[k] 个新子序列,每个都以 s[k] 结尾。
类似地,在位置 i,当我们添加 s[i],如果 s[i] == s[k],那么添加 s[i] 所创建的新子序列中,有一部分与添加 s[k] 时创建的相同,如果这些序列在 s[k] 和 s[i] 之间没有字符,但等等。
实际上,重复项正好是当 s[k] 被添加时创建的那些子序列,也就是在位置 k 之前存在的子序列数量,即 dp[k](对于前缀 s[0..k-1])。
在基于 0 的索引中:
设 j 是 s[i] 最后出现的位置。
那么,在位置 j 添加 s[j] 时,新子序列的数量是 dp[j],但 dp[j] 是考虑 s[0..j-1] 的数量?不是。
标准方式:设 dp[i] 表示考虑前 i 个字符的数量(s[0] 到 s[i-1])。
那么对于每个字符添加在位置 i(处理 s[i]),我们设置 dp[i+1]。
c = s[i]
dp[i+1] = 2 * dp[i]
如果 c 之前出现过,设 j 是最后一次出现的索引(0 到 i-1),那么:
dp[i+1] -= dp[j] # 但 dp[j] 是考虑前 j 个字符,也就是在添加 s[j] 之前。
当 s[j] 被添加时,新子序列的数量是 dp[j],因为添加 s[j] 时,我们添加了 dp[j] 个新序列。
在位置 i,当添加 s[i] 时,如果 s[i] == s[j],那么新序列的数量是 dp[i],但其中一些序列在添加 s[j] 时已经存在,因此重复的数量是 dp[j],但 dp[j] 是添加 s[j] 之前的数量。
在位置 j,添加 s[j] 之后,序列总数是 dp[j+1] = 2 * dp[j]
新序列的数量是 dp[j+1] - dp[j] = dp[j]
类似地,在位置 i,新序列的数量是 dp[i+1] - dp[i] = dp[i]
但由于 s[i] == s[j],新序列与在位置 j 添加时创建的序列相同,如果这些序列在 s[j] 和 s[i] 之间没有变化,但序列是相同的,所以当我们在位置 i 添加 s[i] 时,我们创建的新序列是:所有现有序列 + s[i],但其中一些序列(以 s[i] 结尾的)在位置 j 添加 s[j] 时已经创建了。
所以,重复的数量是位置 j 之前存在的序列数量,也就是 dp[j]。
因此,在位置 i,当我们添加 s[i] 时,新序列的数量是 dp[i],但其中 dp[j] 个是重复的,因此我们减去 dp[j]。
在公式中,对于总数:dp[i+1] = 2 * dp[i] - dp[j]
其中 j 是 s[i] 最后出现的索引。
在“aa”的例子中:s = "a a"
n=2
dp[0] = 1(空)
i=0,c='a',第一次出现,dp[1] = 2 * dp[0] = 2
last_occurrence['a'] = 0
i=1,c='a',在 last_occurrence 中,j=0
dp[2] = 2 * dp[1] - dp[j] = 2*2 - dp[0] = 4 - 1 = 3,正确。
dp[j] 是 dp[0],也就是 1。
在“aba”中:s = "a", "b", "a"
dp[0] = 1
i=0, c='a', 第一次出现, dp[1] = 2*1 = 2, last_occurrence['a']=0
i=1, c='b', 第一次出现, dp[2] = 2*2 = 4, last_occurrence['b']=1
i=2, c='a', 上一次出现 j=0, dp[3] = 2 * dp[2] - dp[j] = 2*4 - dp[0] = 8 - 1 = 7, 正确。
dp[j] 是 dp[0],也就是 1。
在代码中,j 是索引,所以对于 dp[j],但 dp 是存储每个前缀的数量。
在代码中,当 i=2, j=0, dp[j] = dp[0] =1。
完美。
所以,在实现时,我们需要存储每个字符最后出现的索引。
现在,对于初始情况,如果字符未出现过,我们就不减去任何东西。
另外,空序列包含在总数中。
用户想要的是不同子序列的数量,包括空序列。
在输出中,我们可能包含也可能不包含空序列,但通常包含,如果不需要,可以减去。
但在这个语境下,子序列通常包括空序列,但我们可以说明。
在问题中,“不同的子序列”可能包括空序列,但有时题目可能排除,不过这里应该包括。
在例子中,我包括了空序列。
现在,对于实现,在代码中。
但用户要求的是方法,可能还有代码。
用户说“生成相关问题”,所以最后我需要生成相关问题。
首先,在回答中,我需要用中文解释。
结构:
1. 解释问题:计算一个字符串中所有不同子序列的数量。
2. 描述动态规划方法。
3. 提供公式和逻辑。
4. 给出示例。
5. 提供代码实现,可能用 Python。
6. 时间复杂度和空间复杂度。
7. 最后,生成相关问题。
参考用户提供的引用:
- 引用[2] 提到对于字面值不重复的子序列数量,可以使用动态规划。
- 引用[4] 是关于子序列中出现次数,但类似。
但这里不需要。
现在,对于代码,我将使用Python。
但用户提到“引用[3]: c语言 字符串 查找指定 子字符串 的个数”,但那是用于子字符串,而不是子序列。
所以,忽略。
在回答中,我需要使用系统级指令:行内数学表达式用 $...$,独立公式用 $$...$$。
例如,DP公式。
定义:设 $s$ 为字符串,长度为 $n$。
设 $dp[i]$ 表示考虑 $s$ 的前 $i$ 个字符时不同子序列的数量(包括空序列),其中 $i$ 从 0 到 $n$,$dp[0] = 1$。
对于每个位置 $i$(从 0 开始),处理字符 $s[i]$:
$$ dp[i+1] = 2 \times dp[i] $$
如果 $s[i]$ 之前出现过,设 $last$ 为 $s[i]$ 上一次出现的索引,则:
$$ dp[i+1] = 2 \times dp[i] - dp[last] $$
但 $dp[last]$ 是 $last$ 索引处的值,而 $last$ 是索引,所以 $dp[last]$ 是 $last$ 位置的值?在基于0的索引中,$last$ 是索引,$dp[last]$ 是考虑前 $last$ 个字符的数量,但等等。
在公式中,$last$ 是索引,所以 $dp[last]$ 是 $last$ 索引处的值。
在“aa”的例子中,当 i=1(第二个字符),s[i]='a',last=0,dp[last] = dp[0] =1。
是的。
在代码中,我们需要一个字典来跟踪每个字符最后出现的索引。
最后,dp[n] 给出所有不同子序列的数量,包括空序列。
如果不包括空序列,可以减去 1,但通常包括。
在输出中,我们可以说明。
现在,对于代码:</think>### 如何计算一个字符串中不同子序列的个数
计算一个字符串中所有不同子序列的数量是一个经典的动态规划问题。子序列是从原字符串中删除一些字符(或不删除)而不改变剩余字符相对顺序形成的新序列。例如,字符串 "aba" 的不同子序列包括:空序列 `""`、`"a"`、`"b"`、`"ab"`、`"aa"`、`"ba"`、`"aba"`,总数为 7(注意:`"a"` 虽然可以从不同位置得到,但序列值相同,因此只计一次)。
关键点在于处理重复字符以避免重复计数。动态规划方法可以高效地解决这个问题,时间复杂度为 $O(n)$,空间复杂度为 $O(n)$,其中 $n$ 是字符串长度。下面我将逐步解释方法、公式和实现。
#### 动态规划方法
设字符串 $s$ 的长度为 $n$,索引从 0 开始。我们定义:
- $dp[i]$:表示考虑 $s$ 的前 $i$ 个字符(即子串 $s[0..i-1]$)时,不同子序列的数量(包括空序列)。其中 $i$ 从 0 到 $n$:
- $dp[0] = 1$,对应空序列。
- $last\_occurrence$:一个字典,用于存储每个字符最后一次出现的索引。初始时,所有字符的 $last\_occurrence$ 为 -1(表示未出现过)。
对于每个位置 $i$(从 0 到 $n-1$),处理字符 $c = s[i]$:
1. 计算 $dp[i+1]$ 的初始值:$dp[i+1] = 2 \times dp[i]$。这表示新增的子序列包括:
- 所有不包含 $c$ 的子序列(数量为 $dp[i]$)。
- 所有包含 $c$ 的子序列(通过将 $c$ 追加到每个现有子序列末尾得到,数量也为 $dp[i]$)。
2. 如果 $c$ 之前出现过(即 $last\_occurrence[c]$ 不为 -1),则需要减去重复计数的部分:
- 设 $last\_index = last\_occurrence[c]$,即 $c$ 上一次出现的索引。
- 重复的数量等于在 $last\_index$ 处添加 $c$ 时新创建的子序列数量,即 $dp[last\_index]$(因为当 $c$ 在 $last\_index$ 被添加时,它基于前 $last\_index$ 个字符的子序列创建了新序列)。
- 因此,调整公式:$dp[i+1] = 2 \times dp[i] - dp[last\_index]$。
3. 更新 $last\_occurrence[c] = i$,记录当前字符 $c$ 的最新位置。
最终,$dp[n]$ 给出所有不同子序列的数量(包括空序列)。如果不需空序列,可减去 1。
##### 公式总结
- 初始化:$dp[0] = 1$,$last\_occurrence = \{\}$
- 对于 $i$ 从 0 到 $n-1$:
- $$ dp[i+1] = 2 \times dp[i] $$
- 如果 $s[i]$ 在 $last\_occurrence$ 中存在:
- $$ dp[i+1] = 2 \times dp[i] - dp[last\_occurrence[s[i]]] $$
- 更新 $last\_occurrence[s[i]] = i$
#### 示例说明
以 $s = \text{"aba"}$ 为例($n=3$):
1. $i=0$,$c=\text{'a'}$:
- $dp[1] = 2 \times dp[0] = 2 \times 1 = 2$(子序列:`""`, `"a"`)
- $last\_occurrence[\text{'a'}] = 0$
2. $i=1$,$c=\text{'b'}$:
- $dp[2] = 2 \times dp[1] = 2 \times 2 = 4$(新增 `"b"`, `"ab"`;总序列:`""`, `"a"`, `"b"`, `"ab"`)
- $last\_occurrence[\text{'b'}] = 1$
3. $i=2$,$c=\text{'a'}$:
- $dp[3] = 2 \times dp[2] = 2 \times 4 = 8$(初始)
- 但 $\text{'a'}$ 出现过,$last\_index = 0$,$dp[last\_index] = dp[0] = 1$
- $dp[3] = 8 - 1 = 7$(总序列:`""`, `"a"`, `"b"`, `"ab"`, `"aa"`, `"ba"`, `"aba"`)
结果:7 个不同子序列。
#### Python 代码实现
```python
def count_distinct_subsequences(s):
n = len(s)
if n == 0:
return 1 # 只有空序列
dp = [0] * (n + 1)
dp[0] = 1 # 空序列
last_occurrence = {} # 存储字符最后一次出现的索引
for i in range(n):
c = s[i]
# 默认情况:添加当前字符
dp[i + 1] = 2 * dp[i]
# 如果字符重复,减去重复计数
if c in last_occurrence:
last_index = last_occurrence[c]
dp[i + 1] -= dp[last_index] # 注意:dp[last_index] 是 last_index 处的值
# 更新当前字符的最后出现位置
last_occurrence[c] = i
return dp[n] # 包括空序列
# 示例用法
s = "aba"
count = count_distinct_subsequences(s)
print(f"字符串 '{s}' 的不同子序列数量: {count}") # 输出: 7
```
#### 说明
- **时间复杂度**:$O(n)$,遍历字符串一次。
- **空间复杂度**:$O(n)$,用于 `dp` 数组;字典 `last_occurrence` 最多 $O(|\Sigma|)$($\Sigma$ 是字符集),通常视为常数。
- **包含空序列**:代码返回的数量包括空序列。如果不需要,可在结果中减 1(例如,`count - 1`)。
- **边界情况**:空字符串返回 1(只有空序列);所有字符唯一时,子序列数量为 $2^n$(每个字符选或不选)。
此方法高效且易于实现,基于动态规划处理重复字符的重复计数问题[^2]。