最长回文子序列的变种:统计所有回文子序列的个数(进阶版:考虑重复子序列的统计)
题目描述
给定一个字符串 s,统计其中所有不同的非空回文子序列的个数。注意,即使两个回文子序列的内容相同,但如果它们在原字符串中的位置不同(即起始和结束下标不同),也被视为不同的子序列。结果可能很大,需要对 10^9 + 7 取模。
例如,对于字符串 "bccb",不同的回文子序列包括:
"b"(位置0)、"b"(位置3)、"c"(位置1)、"c"(位置2)、"cc"、"bcb"、"bccb"
总共有7个不同的回文子序列。
解题思路
这是一个线性动态规划问题,我们需要统计所有不同的回文子序列。为了避免重复计数,我们使用动态规划来记录以每个字符开头和结尾的回文子序列数量。关键在于处理相同字符的情况,确保不重复计算相同的子序列。
步骤详解
-
定义状态
定义dp[i][j]为字符串s从索引i到j的子串中,所有不同的回文子序列的个数。注意,这里统计的是不同的子序列,即使内容相同但位置不同也算不同。 -
状态转移方程
考虑子串s[i:j]:- 如果
s[i] == s[j],那么我们可以将s[i]和s[j]作为新的回文子序列的边界。此时,所有在i+1到j-1之间的回文子序列都可以被扩展成新的回文子序列,同时还要加上s[i]和s[j]自身形成的子序列。 - 如果
s[i] != s[j],那么我们需要减去重复计算的部分,即dp[i+1][j-1]中的子序列。
具体来说:
- 当
s[i] == s[j]时,dp[i][j] = dp[i+1][j] + dp[i][j-1] - dp[i+1][j-1] + (dp[i+1][j-1] + 1)
简化后:dp[i][j] = dp[i+1][j] + dp[i][j-1] + 1 - 当
s[i] != s[j]时,dp[i][j] = dp[i+1][j] + dp[i][j-1] - dp[i+1][j-1]
这里加1是因为当
s[i] == s[j]时,s[i]和s[j]可以形成一个新的回文子序列。 - 如果
-
初始化
对于所有单个字符,即i == j的情况,dp[i][i] = 1,因为单个字符本身就是一个回文子序列。 -
计算顺序
由于dp[i][j]依赖于dp[i+1][j]、dp[i][j-1]和dp[i+1][j-1],我们需要从子串长度从小到大的顺序进行计算。即先计算所有长度为1的子串,然后是长度为2,依此类推,直到整个字符串。 -
结果提取
最终结果是dp[0][n-1],其中n是字符串s的长度。
示例演算
以字符串 "bccb" 为例:
- 初始化:所有单个字符的
dp[i][i] = 1。 - 计算长度为2的子串:
"bc":s[0] != s[1],所以dp[0][1] = dp[1][1] + dp[0][0] - dp[1][0] = 1 + 1 - 0 = 2(子序列:"b","c")"cc":s[1] == s[2],所以dp[1][2] = dp[2][2] + dp[1][1] + 1 = 1 + 1 + 1 = 3(子序列:"c","c","cc")"cb":s[2] != s[3],所以dp[2][3] = dp[3][3] + dp[2][2] - dp[3][2] = 1 + 1 - 0 = 2(子序列:"c","b")
- 计算长度为3的子串:
"bcc":s[0] != s[2],所以dp[0][2] = dp[1][2] + dp[0][1] - dp[1][1] = 3 + 2 - 1 = 4(子序列:"b","c","c","cc","bc","cc"? 这里需要仔细去重)"ccb":s[1] != s[3],所以dp[1][3] = dp[2][3] + dp[1][2] - dp[2][2] = 2 + 3 - 1 = 4(子序列:"c","c","b","cc","cb","ccb"?)
- 计算整个字符串
"bccb":s[0] == s[3],所以dp[0][3] = dp[1][3] + dp[0][2] + 1 = 4 + 4 + 1 = 9,但实际只有7个不同的回文子序列。
通过这个例子可以看出,直接使用上述状态转移方程可能会重复计算一些子序列。因此,我们需要更精细的去重处理。
优化去重
当 s[i] == s[j] 时,我们需要确保不重复计算中间相同的子序列。具体来说,我们可以在 s[i+1] 到 s[j-1] 之间找到第一个和最后一个与 s[i] 相同的字符,然后调整状态转移方程以避免重复。
修正后的状态转移方程:
- 当
s[i] == s[j]时:- 设
left为i+1到j-1之间第一个等于s[i]的索引,right为最后一个等于s[i]的索引。 - 如果
left > right,说明中间没有相同的字符,那么dp[i][j] = 2 * dp[i+1][j-1] + 2 - 如果
left == right,说明中间有一个相同的字符,那么dp[i][j] = 2 * dp[i+1][j-1] + 1 - 如果
left < right,说明中间有多个相同的字符,那么dp[i][j] = 2 * dp[i+1][j-1] - dp[left+1][right-1]
- 设
- 当
s[i] != s[j]时,dp[i][j] = dp[i+1][j] + dp[i][j-1] - dp[i+1][j-1]
这样,我们可以准确统计所有不同的回文子序列,而不会重复计算。
最终答案
通过上述动态规划过程,我们可以得到字符串 s 中所有不同的回文子序列的个数,并对 10^9 + 7 取模。