题目描述
给定一个字符串 s ,请计算这个字符串中有多少个回文子字符串。 具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。
示例
输入:s = "aab"
输出:4
解释:6个回文子串: "a", "a", "b", "aa"
解题思路
在这个问题中,我们定义一个二维数组 dp[i][j]
来表示字符串 s
从索引 i
到索引 j
的子串 是否是回文子串。
动态规划的状态定义
dp[i][j]
:如果s
从i到j的子串 是回文子串,则dp[i][j]
为true
,否则为false
。
初始化
- 对于所有的
i
,dp[i][i]
应该是true
,因为单个字符一定是回文。 - 对于所有相邻的字符
s[i]
和s[i+1]
,如果它们相等,则dp[i][i+1]
为true
,否则为false
。
状态转移方程
- 如果
s[i] == s[j]
并且j - i > 1
,则dp[i][j]
的值取决于dp[i+1][j-1]
,即dp[i][j] = dp[i+1][j-1]
。 - 否则,
dp[i][j]
为false
。
计数
在填充 dp
数组的过程中,每当发现 dp[i][j]
为 true
时,我们就增加回文子串的计数。
详细步骤:
1. 初始化
- 创建一个二维布尔数组
dp
,大小为n x n
,其中n
是字符串s
的长度。 - 初始化
dp[i][i]
为true
,因为任何单个字符都是回文。 - 如果字符串
s
的长度大于 1,检查所有相邻字符对s[i]
和s[i+1]
,如果它们相等,则将dp[i][i+1]
设置为true
。
2. 填充 DP 表格
- 使用两个嵌套的循环来遍历所有可能的子串长度
len
(从 2 到n
)和起始位置i
(从 0 到n - len
)。 - 对于每个
len
和i
,计算结束位置j = i + len - 1
。 - 如果
s[i] == s[j]
,则检查子串s[i+1...j-1]
是否是回文(即dp[i+1][j-1]
是否为true
)。- 如果是,那么
s[i...j]
也是回文,将dp[i][j]
设置为true
。 - 如果不是,或者
s[i] != s[j]
,则s[i...j]
不是回文,将dp[i][j]
设置为false
(但这一步通常是多余的,因为未初始化的布尔值默认为false
)。
- 如果是,那么
- 在每次将
dp[i][j]
设置为true
时,增加回文子串的计数。
3. 计数回文子串
- 在填充
dp
表格的过程中,每当发现一个回文子串(即将dp[i][j]
设置为true
时),就增加计数。
4. 返回结果
- 完成所有计算后,返回回文子串的总数。
代码示例
class Solution {
public:
int countSubstrings(string s) {
int n = s.length();
int count = 0;
vector<vector<bool>> dp(n, vector<bool>(n, false));
// 初始化
for (int i = 0; i < n; i++) {
dp[i][i] = true;
count++; // 单个字符是回文
if (i < n - 1 && s[i] == s[i + 1]) {
dp[i][i + 1] = true;
count++; // 两个相邻字符相等也是回文
}
}
// 填充 dp 数组
for (int len = 3; len <= n; len++) { // 枚举子串长度
for (int i = 0; i <= n - len; i++) {
int j = i + len - 1;
if (s[i] == s[j] && dp[i + 1][j - 1]) {
dp[i][j] = true;
count++;
}
}
}
return count;
}
};