【LeetCode 5】最长回文子串:中心扩展法,一次就能看懂

10 阅读2分钟

一、题目描述

给定一个字符串 s,请你找出其中最长的回文子串。

回文串的定义是:
从左往右读,和从右往左读是一样的字符串。

示例:

输入:s = "babad"
输出:"bab"
说明:"aba" 也是一个合法答案
输入:s = "cbbd"
输出:"bb"

二、核心思路:中心扩展法

这道题的经典解法之一是 中心扩展法(Expand Around Center)

核心思想非常直观:

  1. 回文串一定有一个“中心”
  2. 从中心向左右两边同时扩展
  3. 只要左右字符相等,就可以继续扩展
  4. 扩展到不能扩展为止,当前得到一个回文串

回文中心的两种情况

回文串的中心有两种可能:

  1. 奇数长度回文
    例如 "aba",中心是 b
  2. 偶数长度回文
    例如 "abba",中心在两个 b 中间

所以我们需要 对每一个位置,尝试两种中心扩展方式


三、整体算法流程

  1. 遍历字符串中每一个位置 i
  2. (i, i) 为中心,向两边扩展(奇数回文)
  3. (i, i + 1) 为中心,向两边扩展(偶数回文)
  4. 取两种扩展中更长的回文长度
  5. 如果比当前记录的最长回文还长,就更新答案

四、代码实现(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)