最长回文子串(LeetCode 5)详解

题目描述

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

如果字符串的反序与原始字符串相同,则该字符串称为回文字符串。

示例 1:

text

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

示例 2:

text

复制代码
输入:s = "cbbd"
输出:"bb"

提示:

  • 1 <= s.length <= 1000

  • s 仅由数字和英文字母组成


思路分析

本题是字符串处理的经典问题,主要有以下三种解法:

解法 时间复杂度 空间复杂度 适用场景
暴力枚举 O(n³) O(1) 仅用于理解
中心扩展法 O(n²) O(1) 简单直观,面试首选
动态规划 O(n²) O(n²) 需要打印所有回文子串时
Manacher 算法 O(n) O(n) 对性能要求极高时

下文将重点讲解 中心扩展法动态规划法


方法一:中心扩展法(推荐)

核心思想

回文串一定是对称的。我们可以枚举每一个可能的"回文中心",然后向左右两边扩展,直到两边的字符不相等为止。由于回文串长度可能为奇数或偶数,中心可能是一个字符(奇数长度)或两个字符之间(偶数长度)。

对于长度为 n 的字符串,共有 2n - 1 个这样的中心。

算法步骤

  1. 遍历每个中心位置(02n-2)。

  2. 对于每个中心,确定初始左右指针:

    • 若中心为字符:left = center / 2right = left

    • 若中心为间隙:left = center / 2right = left + 1

  3. 向两边扩展,当 s[left] == s[right] 时继续扩大范围。

  4. 每次扩展后,若当前回文长度大于记录的最大值,则更新起始位置和最大长度。

  5. 最终返回最长回文子串。

代码实现

Java
java 复制代码
class Solution {
    public String longestPalindrome(String s) {
        // 1. 边界处理:空串或 null 直接返回空字符串
        if (s == null || s.length() < 1) {
            return "";
        }
        
        // 2. 记录当前找到的最长回文子串的起始和结束索引
        int start = 0, end = 0;  // 初始假设第一个字符就是最长回文(长度为1)
        
        // 3. 遍历每一个可能的中心点(包括字符本身和字符间的空隙)
        for (int i = 0; i < s.length(); i++) {
            // 3.1 以 i 为字符中心(处理奇数长度回文,如 "aba")
            int len1 = expandCenter(s, i, i);
            // 3.2 以 i 和 i+1 之间的空隙为中心(处理偶数长度回文,如 "abba")
            int len2 = expandCenter(s, i, i + 1);
            
            // 3.3 取两种中心方式中较长的长度
            int len = Math.max(len1, len2);
            
            // 3.4 如果找到更长的回文,更新起止索引
            if (len > end - start) {   // 注意:end - start 代表当前最大长度-1(因为索引从0开始)
                // 反推起始索引:i 是中心,左边有 (len-1)/2 个字符
                start = i - (len - 1) / 2;
                // 反推结束索引:i + 右边字符数
                end = i + len / 2;
            }
        }
        // 4. 根据记录的起止索引截取子串(注意 substring 的结束索引是不包含的,所以要 +1)
        return s.substring(start, end + 1);
    }
    
    /**
     * 从中心向两边扩展,返回能形成的最长回文子串的【长度】
     * @param s     原字符串
     * @param left  向左扩展的起始指针
     * @param right 向右扩展的起始指针
     * @return      回文子串的长度
     */
    public int expandCenter(String s, int left, int right) {
        // 只要左右指针不越界,并且指向的字符相等,就继续向外扩
        while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)) {
            left--;   // 左指针左移
            right++;  // 右指针右移
        }
        // 循环退出时,left 和 right 都多走了一步(指向不相等或越界的位置)
        // 因此实际回文串的长度为:(right - 1) - (left + 1) + 1 = right - left - 1
        return right - left - 1;
    }
}

复杂度分析

  • 时间复杂度:O(n²),每次扩展最多 O(n) 次比较。

  • 空间复杂度:O(1),只使用了常数变量。


方法二:动态规划

核心思想

定义 dp[i][j] 表示子串 s[i..j] 是否为回文串。则状态转移方程为:

text

复制代码
dp[i][j] = (s[i] == s[j]) && (j - i < 3 || dp[i+1][j-1])

即:若首尾字符相同,且去掉首尾后子串也为回文(或长度小于等于 3 直接成立),则当前子串为回文。

算法步骤

  1. 初始化一个 n × n 的布尔型 dp 数组,默认全为 false

  2. 单个字符(i == j)必然是回文,置 true

  3. 按子串长度从小到大进行递推(len = 2n)。

  4. 对于每个长度,遍历所有可能的起始位置 i,计算结束位置 j = i + len - 1

  5. dp[i][j]truelen > maxLen,则更新最长回文子串的起始位置和长度。

  6. 返回最长回文子串。

代码实现

Java
java 复制代码
class Solution {
    public String longestPalindrome(String s) {
        int n = s.length();
        if (n < 2) return s;
        
        boolean[][] dp = new boolean[n][n];
        int start = 0, maxLen = 1;
        
        // 单个字符均为回文
        for (int i = 0; i < n; i++) {
            dp[i][i] = true;
        }
        
        // 按长度递推
        for (int len = 2; len <= n; len++) {
            for (int i = 0; i + len - 1 < n; i++) {
                int j = i + len - 1;
                if (s.charAt(i) != s.charAt(j)) {
                    dp[i][j] = false;
                } else {
                    if (j - i < 3) {
                        dp[i][j] = true;   // 长度为 2 或 3 且两端相等,直接为回文
                    } else {
                        dp[i][j] = dp[i + 1][j - 1];
                    }
                }
                if (dp[i][j] && len > maxLen) {
                    start = i;
                    maxLen = len;
                }
            }
        }
        return s.substring(start, start + maxLen);
    }
}

复杂度分析

  • 时间复杂度:O(n²),双重循环。

  • 空间复杂度 :O(n²),dp 数组占用。


方法三:Manacher 算法(线性时间)

Manacher 算法可在 O(n) 时间内求出最长回文子串,但实现较为复杂。核心思路是利用回文的对称性,通过已计算的臂长信息避免重复扩展。有兴趣的读者可参考专门讲解 Manacher 算法的文章。


总结对比

方法 优点 缺点
中心扩展法 代码简洁,空间 O(1),易于手写 时间复杂度 O(n²),最坏情况较慢
动态规划 易于理解,可得到所有子串的回文状态 空间消耗 O(n²)
Manacher 时间复杂度 O(n),最优 实现复杂,不易在面试中写出

对于绝大多数场景(包括面试),中心扩展法 已足够应对,且是推荐的首选解法。


扩展思考

  • 如果要求返回 所有 最长回文子串(可能有多个),该如何修改代码?

  • 如果字符串长度达到 10⁵ 级别,必须使用 Manacher 算法。

  • 最长回文子序列 与 最长回文子串 有何区别?(子序列不要求连续)

相关推荐
paeamecium3 小时前
【PAT甲级真题】- Cars on Campus (30)
数据结构·c++·算法·pat考试·pat
chh5634 小时前
C++--模版初阶
c语言·开发语言·c++·学习·算法
RTC老炮5 小时前
带宽估计算法(gcc++)架构设计及优化
网络·算法·webrtc
dsyyyyy11015 小时前
计数孤岛(DFS和BFS解决)
算法·深度优先·宽度优先
会编程的土豆5 小时前
01背包与完全背包详解
开发语言·数据结构·c++·算法
汀、人工智能6 小时前
[特殊字符] 第86课:最大正方形
数据结构·算法·数据库架构·图论·bfs·最大正方形
hetao17338376 小时前
2026-04-12~14 hetao1733837 的刷题记录
c++·算法
lxh01136 小时前
正则表达式匹配
算法
SuperChe6 小时前
用AI Native的方式优化前端性能
前端·算法