一、题目

二、做题思路
2.1 状态表示(核心基础)
本题要求统计字符串中回文子串的总数 。定义 dp[i][j] 表示子串 s[i..j](包含两端)是否为回文串 (true 表示是,false 表示否)。该二维表可覆盖所有连续子串。
2.2 状态转移方程(关键难点)
判断 s[i..j] 是否为回文串,需满足两个条件:
-
首尾字符相等:
s[i] == s[j]; -
内部子串
s[i+1..j-1]为回文串,或子串长度小于等于 2(此时无需检查内部)。因此转移为:
dp[i][j] = (s[i] == s[j]) && (i+1 > j-1 ? true : dp[i+1][j-1]),即当i+1 >= j-1(长度 ≤ 2)时直接为true,否则依赖dp[i+1][j-1]。
2.3 初始化(边界防护)
所有 dp[i][j] 初始化为 false。长度 1 的子串(i == j)必然回文,会在递推中被正确置 true(因为首尾相同且内部为空)。
2.4 填表顺序(递推方向)
dp[i][j] 依赖 dp[i+1][j-1](左下位置),因此 从下到上 (i 从 n-1 到 0),且内层 从左到右 (j 从 i 到 n-1),确保 dp[i+1][j-1] 已计算。
2.5 返回值(目标映射)
遍历所有 dp[i][j],每当为 true 时计数器 ret++,最终 返回 ret,即为回文子串总数。
三、代码
cpp
class Solution
{
public:
int countSubstrings(string s)
{
int n = s.size();
// 1. 创建dp表
// dp[i][j] 表示子串 s[i..j] 是否是回文串(i <= j)
// 初始化为 false
vector<vector<bool>> dp(n, vector<bool>(n, false));
int ret = 0; // 记录回文子串的总数
// 2. 填表顺序:从下往上(i 从 n-1 到 0),从左往右(j 从 i 到 n-1)
// 因为 dp[i][j] 依赖于 dp[i+1][j-1](更短的内部子串),
// 所以需要先计算行号更大的 dp,即 i 递减方向遍历。
for (int i = n - 1; i >= 0; i--)
{
for (int j = i; j < n; j++)
{
// 3. 状态转移方程:
// 若 s[i] == s[j],则 s[i..j] 是否为回文取决于内部子串 s[i+1..j-1] 是否为回文,
// 但当 i+1 > j-1(即长度 ≤ 2)时,直接判定为 true。
if (s[i] == s[j])
{
dp[i][j] = (i + 1 < j) ? dp[i + 1][j - 1] : true;
}
// 若 s[i] != s[j],dp[i][j] 保持 false(已默认)
// 4. 若当前子串是回文,累加计数
if (dp[i][j] == true)
{
ret++;
}
}
}
// 5. 返回值:回文子串的总个数
return ret;
}
};
四、流程图

五、题目

六、做题思路
6.1 状态表示(核心基础)
本题要求返回最长的回文子串 。定义 dp[i][j] 表示子串 s[i..j](包含两端)是否为回文串 (true 表示是,false 表示否)。通过二维表覆盖所有连续子串,便于记录最长结果。
6.2 状态转移方程(关键难点)
判断 s[i..j] 是否为回文,需满足:
-
首尾字符相等:
s[i] == s[j]; -
若子串长度 ≤ 2(即
i+1 >= j),则直接为true;否则需内部子串s[i+1..j-1]也为回文,即dp[i+1][j-1] == true。因此转移为:
dp[i][j] = (s[i] == s[j]) && (i+1 >= j ? true : dp[i+1][j-1])。
6.3 初始化(边界防护)
所有 dp[i][j] 初始化为 false。长度 1 的子串(i == j)会在递推中因首尾相同且长度≤2被置为 true。
6.4 填表顺序(递推方向)
dp[i][j] 依赖 dp[i+1][j-1](左下位置),因此 从下到上 (i 从 n-1 到 0),内层 从左到右 (j 从 i 到 n-1),确保内部子串已先计算。
6.5 返回值(目标映射)
在遍历过程中,每当 dp[i][j] == true 且当前长度 j-i+1 大于已记录的最长长度 len 时,更新 len 和起始下标 begin。最终 返回 s.substr(begin, len),即最长回文子串。
七、代码
cpp
class Solution
{
public:
string longestPalindrome(string s)
{
int n = s.size();
// 1. 创建dp表
// dp[i][j] 表示子串 s[i..j] 是否为回文串(i <= j)
// 初始化为 false,表示默认不是回文
vector<vector<bool>> dp(n, vector<bool>(n, false));
int len = 1; // 当前最长回文子串的长度
int begin = 0; // 当前最长回文子串的起始下标
// 2. 填表顺序:从下往上(i 从 n-1 到 0),从左往右(j 从 i 到 n-1)
// 因为 dp[i][j] 依赖于 dp[i+1][j-1](更短的内部子串),
// 所以需要先计算行号更大的 dp,即 i 递减。
for (int i = n - 1; i >= 0; i--)
{
for (int j = i; j < n; j++)
{
// 3. 状态转移方程:
// 只有当 s[i] == s[j] 时,才可能成为回文。
// 若子串长度 ≤ 2(即 i+1 >= j),则直接判定为 true;
// 否则取决于内部子串 s[i+1..j-1] 是否为回文。
if (s[i] == s[j])
{
dp[i][j] = (i + 1 < j) ? dp[i + 1][j - 1] : true;
}
// 若 s[i] != s[j],dp[i][j] 保持 false
// 4. 若当前子串是回文且长度大于已记录的最长长度,则更新记录
if (dp[i][j] && (j - i + 1) > len)
{
len = j - i + 1;
begin = i;
}
}
}
// 5. 返回值:截取并返回最长回文子串
return s.substr(begin, len);
}
};
八、流程图
