【中等】5. 最长回文子串

0 阅读3分钟

给你一个字符串 s,找到 s 中最长的 回文 子串。 示例 1:

输入: s = "babad"
输出: "bab"
解释: "aba" 同样是符合题意的答案。

示例 2:

输入: s = "cbbd"
输出: "bb"

提示:

  • 1 <= s.length <= 1000
  • s 仅由数字和英文字母组成

🏠 生活案例:寻找最对称的“折纸”

想象你面前有一长串珠子,每颗珠子上都有一个字母(比如 b a b a d)。你想在这串珠子里找到最长的一段,它是对称的——也就是说,从左往右看和从右往左看完全一样。

这段代码采用的方法叫做 “中心扩散法”

生活化的操作步骤:

  1. 选中心:你盯着其中一颗珠子(或者两颗珠子中间的缝隙)。
  2. 向外看:你的左右手同时向两边移动。
  3. 比对:只要左手和右手摸到的珠子是一样的,你就继续往外扩。
  4. 停止:一旦发现两边珠子不一样,或者摸到了尽头,你就停下来,记录下刚才这一段有多长。
  5. 比较:换下一颗珠子作为中心,重复操作。最后看哪次扩出来的最长。

💻 代码实现与生活化注释

这段代码非常巧妙地处理了回文串的两种情况:一种是像 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 循环退出时,leftright 已经多走了一步,
        // 所以 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)

如果不考虑这两种中心,你就会漏掉一半的可能性!


性能分析

  • 时间复杂度O(n2)O(n^2)。我们需要遍历 nn 个中心,每个中心最多向外扩散 nn 次。虽然比不上最高级的 Manacher 算法(O(n)O(n)),但它的逻辑最清晰,是面试中最推荐的解法。
  • 空间复杂度O(1)O(1)。除了存储结果的字符串,我们只用了几个指针,不需要额外的表格。