给你一个字符串 s,找到 s 中最长的 回文 子串。
示例 1:
输入: s = "babad"
输出: "bab"
解释: "aba" 同样是符合题意的答案。
示例 2:
输入: s = "cbbd"
输出: "bb"
提示:
1 <= s.length <= 1000s仅由数字和英文字母组成
🏠 生活案例:寻找最对称的“折纸”
想象你面前有一长串珠子,每颗珠子上都有一个字母(比如 b a b a d)。你想在这串珠子里找到最长的一段,它是对称的——也就是说,从左往右看和从右往左看完全一样。
这段代码采用的方法叫做 “中心扩散法” 。
生活化的操作步骤:
- 选中心:你盯着其中一颗珠子(或者两颗珠子中间的缝隙)。
- 向外看:你的左右手同时向两边移动。
- 比对:只要左手和右手摸到的珠子是一样的,你就继续往外扩。
- 停止:一旦发现两边珠子不一样,或者摸到了尽头,你就停下来,记录下刚才这一段有多长。
- 比较:换下一颗珠子作为中心,重复操作。最后看哪次扩出来的最长。
💻 代码实现与生活化注释
这段代码非常巧妙地处理了回文串的两种情况:一种是像 aba(中心是一个点),另一种是像 abba(中心是一条缝)。
JavaScript
/**
* @param {string} s
* @return {string}
*/
var longestPalindrome = function (s) {
// 1. 如果字符串长度小于 2,它本身就是回文,直接还回去
if (s.length < 2) return s;
let result = '';
// 2. 遍历每一个位置,把它当作“中心”
for (let i = 0; i < s.length; i++) {
// 情况 A:中心是一个字符(奇数长度,如 "aba")
subString(i, i);
// 情况 B:中心是两个字符中间的空隙(偶数长度,如 "abba")
subString(i, i + 1);
}
// 这是一个专门负责“向外扩散”的助手函数
function subString(left, right) {
// 3. 只要左右指针没越界,且两边的珠子一模一样
while (left >= 0 && right < s.length && s[left] === s[right]) {
// 继续向外伸展
left--;
right++;
}
// 4. 扩不动了,把刚才那段“对称”的珠子截取出来
// 注意:因为上面 while 循环退出时,left 和 right 已经多走了一步,
// 所以 slice 的范围是 [left + 1, right)
let currentSub = s.slice(left + 1, right);
// 5. 如果这次发现的比之前记录的最长回文还要长,就更新它
if (result.length < currentSub.length) {
result = currentSub;
}
}
// 6. 走完所有中心点,交出冠军
return result;
};
🧩 为什么要有 subString(i, i) 和 subString(i, i + 1)?
- 奇数对称
aba:你捏住b往两边看,左边是a,右边是a,完美。这就是subString(i, i)。 - 偶数对称
abba:你得捏住两个b中间的缝隙往两边看。左手捏左边的b,右手捏右边的b。这就是subString(i, i + 1)。
如果不考虑这两种中心,你就会漏掉一半的可能性!
性能分析
- 时间复杂度:。我们需要遍历 个中心,每个中心最多向外扩散 次。虽然比不上最高级的 Manacher 算法(),但它的逻辑最清晰,是面试中最推荐的解法。
- 空间复杂度:。除了存储结果的字符串,我们只用了几个指针,不需要额外的表格。