最长回文子序列的变种:统计不同非空回文子序列的个数
题目描述
给定一个字符串 s,要求统计其中所有不同的非空回文子序列的个数。注意,这里的子序列是指从原字符串中删除零个或多个字符后,剩余字符相对位置不变形成的序列。由于不同位置的相同字符序列被视为同一个子序列,因此需要去重。结果可能很大,需要对 10^9 + 7 取模。
例如,对于字符串 s = "bccb",不同的非空回文子序列有:"b", "c", "bb", "cc", "bcb", "bccb",共 6 个。
解题思路
这是一个线性动态规划问题,关键在于如何避免重复计数。我们可以使用区间动态规划,定义 dp[i][j] 为字符串 s[i..j] 中不同回文子序列的个数。但直接统计会重复计算相同字符组成的子序列。因此,需要记录每个字符首次和最后一次出现的位置,确保在统计时每个字符组合只被计算一次。
详细步骤
-
定义状态
设dp[i][j]表示子串s[i..j]中不同回文子序列的个数。目标是求dp[0][n-1],其中n是字符串长度。 -
状态转移
考虑子串s[i..j]:-
如果
s[i] != s[j]:
回文子序列要么包含s[i]但不包含s[j],要么包含s[j]但不包含s[i],要么两者都不包含。但直接使用dp[i+1][j] + dp[i][j-1] - dp[i+1][j-1]会减去中间部分两次,需要加回。
转移方程:
dp[i][j] = dp[i+1][j] + dp[i][j-1] - dp[i+1][j-1] -
如果
s[i] == s[j](设字符为ch):
此时,所有s[i+1..j-1]中的回文子序列,两端加上ch后仍是回文子序列。此外,还需考虑单独由ch组成的子序列(如"ch"或"chch")。
但需避免重复:如果在s[i+1..j-1]中存在同样以ch开头和结尾的子序列,可能会重复计算。
解决方法是找到子串s[i+1..j-1]中第一个等于ch的位置left和最后一个等于ch的位置right:- 如果
left > right:说明中间没有字符ch,新增的子序列是ch和chch。
转移方程:
dp[i][j] = dp[i+1][j-1] * 2 + 2
(其中+2表示新增"ch"和"chch") - 如果
left == right:中间只有一个ch,新增的子序列是ch和chch,但"ch"可能已被计算过?
实际需修正为:
dp[i][j] = dp[i+1][j-1] * 2 + 1
(+1表示新增"chch",而"ch"已在中间部分统计过) - 如果
left < right:中间有多个ch,需减去s[left+1..right-1]中已统计过的重复子序列。
转移方程:
dp[i][j] = dp[i+1][j-1] * 2 - dp[left+1][right-1]
- 如果
-
-
初始化
单个字符的子串s[i..i]只有一个回文子序列(即字符本身):
dp[i][i] = 1
空子串(i > j)视为 0。 -
遍历顺序
按子串长度从小到大计算,外层循环遍历长度len,内层循环遍历起始位置i。 -
模运算
由于结果可能很大,每次更新dp[i][j]后取模10^9+7。
示例计算(s = "bccb")
- 初始化:所有
dp[i][i] = 1(如dp[0][0]=1表示"b")。 - 长度 2:
"bc":dp[0][1] = dp[1][1] + dp[0][0] - dp[1][0] = 1 + 1 - 0 = 2("b","c")"cc":两端相同,中间无相同字符,dp[1][2] = 1*2 + 2 = 4(实际是"c","cc",但需去重?这里需修正:实际不同子序列只有"c"和"cc",但"c"已统计过,因此+1即可,最终为 2)。
- 逐步计算至整个字符串,最终
dp[0][3] = 6。
关键点
- 去重核心:当两端字符相同时,通过查找中间相同字符的位置,避免重复计数。
- 时间复杂度 O(n²),空间复杂度 O(n²)。