最长回文子序列的变种:统计所有回文子序列的个数(进阶版:考虑字符集限制)
字数 1178 2025-11-17 22:37:06
最长回文子序列的变种:统计所有回文子序列的个数(进阶版:考虑字符集限制)
题目描述:
给定一个字符串s,统计其中所有非空回文子序列的个数。注意,即使两个回文子序列由相同字符组成但位置不同,也被视为不同的子序列。此外,题目还增加了字符集限制:字符串s只包含小写英文字母。
解题过程:
- 问题分析:
- 我们需要统计字符串中所有回文子序列的数量
- 回文子序列是指从原字符串中删除0个或多个字符后,剩余字符按原顺序组成的回文字符串
- 由于是子序列而不是子串,字符可以不连续
- 需要考虑字符集限制(小写英文字母)
-
动态规划定义:
定义dp[i][j]表示字符串s从索引i到j(包含两端)的子串中,回文子序列的个数。 -
状态转移方程:
考虑区间[i, j]:
- 当i > j时,dp[i][j] = 0(空区间)
- 当i = j时,dp[i][j] = 1(单个字符本身就是一个回文子序列)
- 当s[i] ≠ s[j]时:
dp[i][j] = dp[i+1][j] + dp[i][j-1] - dp[i+1][j-1]
解释:用区间[i+1,j]和[i,j-1]的回文子序列数相加,但减去重复计算的区间[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+1][j] + dp[i][j-1] + 1
解释:当两端字符相同时,除了包含之前的所有情况外,还能以s[i]和s[j]作为新的回文子序列的两端,与区间[i+1,j-1]内的所有回文子序列组合形成新的回文子序列,再加上s[i]s[j]本身这个长度为2的回文子序列
- 边界条件:
- 对于所有i > j,dp[i][j] = 0
- 对于所有i = j,dp[i][j] = 1
- 计算顺序:
由于dp[i][j]依赖于dp[i+1][j]、dp[i][j-1]和dp[i+1][j-1],我们需要按照子串长度从小到大的顺序计算:
- 先计算所有长度为1的子串
- 然后计算长度为2的子串
- 依此类推,直到计算整个字符串
-
最终结果:
dp[0][n-1]就是整个字符串中所有回文子序列的数量 -
考虑字符集限制的优化:
由于字符串只包含小写英文字母,我们可以利用这个特性进行优化:
- 预处理每个字符的出现位置
- 在计算过程中,可以利用字符的有限性来优化重复计算
- 算法实现:
def countPalindromicSubsequences(s: str) -> int:
n = len(s)
MOD = 10**9 + 7
dp = [[0] * n for _ in range(n)]
# 初始化长度为1的子串
for i in range(n):
dp[i][i] = 1
# 按长度递增计算
for length in range(2, n + 1):
for i in range(n - length + 1):
j = i + length - 1
if s[i] == s[j]:
dp[i][j] = (dp[i+1][j] + dp[i][j-1] + 1) % MOD
else:
dp[i][j] = (dp[i+1][j] + dp[i][j-1] - dp[i+1][j-1]) % MOD
return dp[0][n-1]
- 复杂度分析:
- 时间复杂度:O(n²),需要填充n×n的DP表
- 空间复杂度:O(n²),用于存储DP表
这个解法能够准确统计字符串中所有回文子序列的数量,同时考虑了字符集限制带来的优化可能性。