区间动态规划例题:统计不同非空回文子序列个数问题(字符集限制版)
题目描述:
给定一个字符串 s,由字符 'a', 'b', 'c', 'd' 四种字符组成(即字符集大小为4),长度不超过1000。要求统计 s 中不同非空回文子序列的个数。由于答案可能很大,返回结果对 10^9 + 7 取模。
注意:子序列是通过删除原字符串某些字符(可以不删除)而不改变剩余字符相对顺序形成的新字符串。如果某个回文子序列在内容上相同,即使来自原字符串的不同位置,也视为同一个子序列。
解题过程循序渐进讲解
步骤1:理解问题核心
我们需要统计所有不同的回文子序列。例如 s = "bccb",回文子序列有:
"b", "c", "bb", "cc", "bcb", "bccb"(注意"ccc"不是回文,"bcb"出现多次但只算一次)。
关键点:不同位置但内容相同的子序列视为同一个。
步骤2:确定DP状态定义
定义 dp[i][j] 表示在子串 s[i..j] 中,所有不同非空回文子序列的个数。
但这样定义会重复计算相同内容但不同位置的子序列,不符合"不同"的要求。
正确状态定义:
设 dp[i][j] 表示在子串 s[i..j] 中,以 s[i] 开头且以 s[j] 结尾的所有不同回文子序列的个数。
但这样还不能直接得到最终答案,需要更精细的状态设计。
步骤3:引入字符维度
由于字符集只有4种,我们可以增加一维表示回文子序列的两端字符:
定义 dp[i][j][c] 表示在子串 s[i..j] 中,以字符 c 开头且以字符 c 结尾的所有不同回文子序列的个数(c ∈ {'a','b','c','d'})。
最终答案 = Σ dp[0][n-1][c] 对所有字符 c 求和。
步骤4:状态转移分析
考虑子串 s[i..j],对于每个字符 c:
- 如果 s[i] ≠ c 或 s[j] ≠ c:
dp[i][j][c] = dp[i+1][j][c] 或 dp[i][j-1][c] 或 dp[i+1][j-1][c]?需要仔细分析。
实际上更精确的状态转移:
- 如果 s[i] ≠ c:dp[i][j][c] = dp[i+1][j][c]
- 如果 s[j] ≠ c:dp[i][j][c] = dp[i][j-1][c]
- 如果 s[i] ≠ c 且 s[j] ≠ c:dp[i][j][c] = dp[i+1][j-1][c] + 调整项
步骤5:完整状态转移方程
定义 f[i][j] 表示子串 s[i..j] 中所有不同回文子序列的个数(这就是最终答案对应的状态)。
我们有:
- f[i][j] = f[i+1][j] + f[i][j-1] - f[i+1][j-1](容斥原理,减去重复部分)
- 如果 s[i] == s[j]:
设 c = s[i],那么以 c 开头和结尾的回文子序列包括:- 单个字符 c(1个)
- c + (s[i+1..j-1] 中的任意回文子序列) + c
所以新增的回文子序列个数 = f[i+1][j-1] + 1
因此:f[i][j] += f[i+1][j-1] + 1
但要避免重复计数,需要更精细的处理。
步骤6:避免重复计数的标准解法
标准解法使用二维DP,但记录每个字符最近出现的位置:
设 dp[i][j] 表示 s[i..j] 中不同回文子序列的个数。
转移方程:
dp[i][j] = dp[i+1][j] + dp[i][j-1] - dp[i+1][j-1](容斥原理)
如果 s[i] == s[j]:
令 left = 下一个等于 s[i] 的位置(i < left < j)
令 right = 前一个等于 s[j] 的位置(i < right < j)
如果 left > right:说明中间没有相同的字符
dp[i][j] += dp[i+1][j-1] + 1
如果 left == right:说明中间有一个相同的字符
dp[i][j] += dp[i+1][j-1] + 1
如果 left < right:说明中间有多个相同的字符
dp[i][j] += dp[left+1][right-1] + 1
步骤7:算法实现步骤
- 预处理每个字符在每个区间内的第一次和最后一次出现位置
- 按区间长度从小到大计算DP
- 对每个区间 [i, j],先计算基础值(容斥原理)
- 如果 s[i] == s[j],根据中间相同字符的情况调整
- 结果取模
步骤8:示例演示
s = "bccb"
计算过程:
- 区间长度1:dp[0][0]=1, dp[1][1]=1, dp[2][2]=1, dp[3][3]=1
- 区间长度2:
[0,1]:"b","c" → dp=2
[1,2]:"c","c" → 中间无相同字符,dp=2+dp[2][1]+1=2+0+1=3
[2,3]:"c","b" → dp=2 - 区间长度3:
[0,2]:s[0]≠s[2],dp=dp[1][2]+dp[0][1]-dp[1][1]=3+2-1=4
[1,3]:s[1]=s[3]='c',中间有相同字符,需要特殊处理... - 最终得到 dp[0][3] = 6
步骤9:复杂度分析
时间复杂度:O(n²)
空间复杂度:O(n²)
这种方法确保了相同内容的回文子序列只被统计一次,符合题目要求。