5.最长回文子串 
1. dp 数组含义
定义二维数组 dp:
如果 dp[i][j] === true,说明:
如果 dp[i][j] === false,说明:
题目要求返回最长回文子串本身,所以在计算 dp 的过程中,还需要记录:
start:当前最长回文子串的起始下标。maxLen:当前最长回文子串的长度。
最后返回:
2. 确定状态转移方程
判断 s[i...j] 是否是回文子串,关键看两端字符:
两端字符不同
如果:
那么 s[i...j] 一定不是回文子串:
两端字符相同
如果:
还需要分两种情况。
第一种,子串长度小于等于 3:
只要两端字符相同,它一定是回文子串。
例如:
所以:
第二种,子串长度大于 3。
此时只看两端字符还不够,还要看去掉两端后的内部子串 s[i + 1...j - 1] 是否是回文子串:
完整转移方程:
一句话总结:一个子串是否是回文,取决于两端字符是否相同,以及去掉两端后的内部子串是否是回文。
3. dp 数组如何初始化
dp[i][j] 表示某个子串是否是回文子串,一开始还没有判断任何子串,所以可以全部初始化为 false:
根据状态定义,单个字符一定是回文子串:
不过在本题代码中,不需要提前单独初始化对角线。
因为遍历子串长度 len = 1 时,s[i] === s[j] 且 len <= 3,会自然把 dp[i][i] 更新为 true。
边界情况:
- 如果
s.length < 2,字符串本身就是最长回文子串,直接返回s。 - 初始设置
start = 0、maxLen = 1,表示至少可以把第一个字符作为当前最长回文子串。
4. 确定遍历顺序
状态转移中:
也就是当前子串依赖它内部更短的子串。
所以必须先计算短子串,再计算长子串。
可以按子串长度从小到大遍历:
这样在计算长度为 len 的子串时,内部子串 s[i + 1...j - 1] 的长度是 len - 2,一定已经在前面计算完成。
5. 举例打印 dp 数组
以:
为例。
下标和字符对应关系:
初始化时,dp 全部为 false:
长度为 1
每个单字符都是回文子串:
此时最长回文子串长度是 1。
长度为 2
检查 "ba"、"ab"、"ba"、"ad",两端字符都不同,所以没有新的回文子串。
长度为 3
检查 "bab":
并且长度为 3,所以:
当前最长回文子串更新为 "bab"。
继续检查 "aba":
并且长度为 3,所以:
"aba" 也是长度为 3 的回文子串。
最终 dp 数组为:
最长回文子串长度是 3,返回 "bab" 或 "aba" 都可以。
由于下面的代码只在发现更长回文子串时更新答案,所以先找到 "bab" 后,不会被同长度的 "aba" 覆盖,最终返回 "bab"。
6. 代码实现
复杂度分析
- 时间复杂度:
O(n^2),需要枚举所有子串。 - 空间复杂度:
O(n^2),需要二维dp数组保存状态。


