线性动态规划:最长回文子串(动态规划解法)的进阶版——统计所有长度至少为L的不同回文子串的个数
题目描述
给定一个字符串 s 和一个正整数 L,要求统计所有长度至少为 L 的不同回文子串的个数。这里的子串是连续的,且不同指的是子串内容不同(即相同的字符串只计数一次)。例如,对于 s = "ababa" 和 L = 2,长度为2或以上的不同回文子串有 "aba"、"bab"、"ababa" 三个,所以答案是3。
解题过程
这是一个字符串处理问题,结合了回文串判断和去重计数。我们需要高效地找出所有长度至少为 L 的回文子串,并去重。动态规划可以用来判断任意子串是否为回文,同时配合哈希集合(如 Python 的 set)来去重。
步骤分解:
-
定义状态
设dp[i][j]表示字符串s从索引i到j的子串(包含两端)是否为回文串。dp[i][j]为布尔值:True表示是回文,False表示不是。- 如果
s[i] == s[j],且去掉两端的子串s[i+1:j-1]是回文(或长度为0/1),则整个子串是回文。 - 转移方程:
dp[i][j] = (s[i] == s[j]) and (j - i <= 2 or dp[i+1][j-1])
其中j - i <= 2表示子串长度为1、2或3时,只要两端字符相等就一定是回文。
- 如果
-
初始化与填表顺序
因为dp[i][j]依赖于dp[i+1][j-1](左下角的状态),我们需要按子串长度从小到大进行递推。- 外层循环枚举子串长度
len,从1到n(n为字符串长度)。 - 内层循环枚举起始位置
i,计算结束位置j = i + len - 1。 - 这样保证计算
dp[i][j]时,dp[i+1][j-1]已经计算过了。
- 外层循环枚举子串长度
-
收集结果并去重
在计算过程中,每当发现一个回文子串s[i:j+1](Python切片是左闭右开,所以是s[i:j+1]),检查其长度是否至少为L。如果是,则将其加入一个集合(set)中,集合自动去重。 -
输出结果
最后,集合的大小就是不同回文子串的个数。
举例说明
以 s = "ababa", L = 2 为例:
- 字符串长度
n = 5。 - 枚举所有子串:
- 长度=2:
"ab"不是回文,"ba"不是,"ab"不是,"ba"不是。 - 长度=3:
"aba"是回文(长度≥2,加入集合),"bab"是(加入),"aba"是(重复,不重复加入)。 - 长度=4:
"abab"不是,"baba"不是。 - 长度=5:
"ababa"是回文(加入集合)。
- 长度=2:
- 集合中有
{"aba", "bab", "ababa"},共3个。
复杂度分析
- 时间复杂度:动态规划填表需要 O(n²),每次子串加入集合(字符串切片)的代价为 O(子串长度),最坏情况下所有子串都是回文(如全相同字符),总共有 O(n²) 个子串,每个子串长度平均 O(n),所以最坏为 O(n³)。但实际中回文子串数量远少于全部子串,且可以通过存储子串的哈希值(如通过字符串哈希)优化到 O(n²)。
- 空间复杂度:O(n²) 用于存储
dp表,以及集合存储最多 O(n²) 个子串(每个子串长度 O(n)),所以空间最坏 O(n³)(如果存储完整子串)。同样可以用哈希值优化到 O(n²)。
优化思路
如果需要处理更长的字符串,可以采用中心扩展法枚举所有回文子串(每个中心点向两边扩展),配合哈希集合去重,时间复杂度为 O(n²),空间为 O(n²)(用于存储哈希值)。不过动态规划解法更直接,易于理解。
总结
这个题目是最长回文子串问题的自然扩展,要求统计所有满足长度限制的不同回文子串。通过动态规划判断所有子串的回文性,并用集合去重,即可得到答案。