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