一、题目描述
给定一个字符串 s,请你找出其中最长的回文子串。
回文串的定义是:
从左往右读,和从右往左读是一样的字符串。
示例:
输入:s = "babad"
输出:"bab"
说明:"aba" 也是一个合法答案
输入:s = "cbbd"
输出:"bb"
二、核心思路:中心扩展法
这道题的经典解法之一是 中心扩展法(Expand Around Center) 。
核心思想非常直观:
- 回文串一定有一个“中心”
- 从中心向左右两边同时扩展
- 只要左右字符相等,就可以继续扩展
- 扩展到不能扩展为止,当前得到一个回文串
回文中心的两种情况
回文串的中心有两种可能:
- 奇数长度回文
例如"aba",中心是b - 偶数长度回文
例如"abba",中心在两个b中间
所以我们需要 对每一个位置,尝试两种中心扩展方式。
三、整体算法流程
- 遍历字符串中每一个位置
i - 以
(i, i)为中心,向两边扩展(奇数回文) - 以
(i, i + 1)为中心,向两边扩展(偶数回文) - 取两种扩展中更长的回文长度
- 如果比当前记录的最长回文还长,就更新答案
四、代码实现(JavaScript)
var longestPalindrome = function(s) {
if (s.length < 2) return s;
let start = 0; // 最长回文的起始位置
let maxLen = 1; // 最长回文的长度
// 从中心向两边扩展
function expand(left, right) {
while (left >= 0 && right < s.length && s[left] === s[right]) {
left--;
right++;
}
// 注意:跳出循环时,left 和 right 都多走了一步
return right - left - 1;
}
for (let i = 0; i < s.length; i++) {
// 奇数长度回文
const len1 = expand(i, i);
// 偶数长度回文
const len2 = expand(i, i + 1);
const len = Math.max(len1, len2);
if (len > maxLen) {
maxLen = len;
// 计算当前回文串的起点
start = i - Math.floor((len - 1) / 2);
}
}
return s.substring(start, start + maxLen);
};
五、关键细节拆解
1. 为什么 expand 返回 right - left - 1
在 while 循环结束时:
left已经多减了一次right已经多加了一次
所以真实回文长度是:
(right - 1) - (left + 1) + 1 = right - left - 1
这是一个非常容易写错的地方。
2. 起始位置为什么这样算
start = i - Math.floor((len - 1) / 2);
这个公式同时适用于:
- 奇数回文
- 偶数回文
举例说明:
"aba",len = 3
起点 =i - 1"abba",len = 4
起点 =i - 1
统一公式可以避免分类讨论,代码更简洁。
六、时间和空间复杂度分析
时间复杂度:
- 外层循环遍历
n次 - 每次扩展最坏是
O(n)
整体时间复杂度为:
O(n²)
空间复杂度:
- 只使用了常数级变量
O(1)