5. 最长回文子串 (Medium)

专题归类: 10-动态规划 LeetCode 链接: https://leetcode.cn/problems/longest-palindromic-substring/


在线做题: 写 Python 代码并运行 · Java 跳转 LeetCode

题目描述

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

回文串是指正着读和反着读都一样的字符串。

示例 1:

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

示例 2:

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

提示:

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

题目详细分析

  • 数据范围:长度 <= 1000,O(n²) 的 DP 或中心扩展可以通过。
  • 核心约束:子串是连续的(与子序列不同);回文要求对称。
  • 边界条件:长度为 1 时,自身就是回文;长度为 2 时,若两字符相等则为回文。
  • 隐藏条件:一个回文去掉首尾字符后仍然是回文——这是区间 DP 的基础。中心扩展法利用回文的对称性效率更高。

小白版直白理解

在一串字符中找一段”正反读都一样”的连续子串。这就像找镜子——如果一段字符左右对称,放面镜子在中间,镜子里外是一样的。比如 “babad” 中的 “bab” 就是对称的。判断是不是回文最简单的方法:掐头去尾后剩下的还是回文,且头尾相等。


解题思路

思路一:中心扩展法(推荐)

核心洞察:回文串一定是对称的,所以每个字符(以及每两个相邻字符之间)都可以作为回文中心,向两边扩展直到不是回文。长度为 n 的字符串有 2n-1 个可能的中心。

def longestPalindrome(s):
    def expand(l, r):
        """从中心向两端扩展,返回最长回文的起止下标"""
        while l >= 0 and r < len(s) and s[l] == s[r]:
            l -= 1
            r += 1
        return l + 1, r - 1
 
    start = end = 0
    for i in range(len(s)):
        # 奇数长度回文中心(一个字符)
        l1, r1 = expand(i, i)
        # 偶数长度回文中心(两个字符之间)
        l2, r2 = expand(i, i + 1)
 
        if r1 - l1 > end - start:
            start, end = l1, r1
        if r2 - l2 > end - start:
            start, end = l2, r2
 
    return s[start:end + 1]

思路二:区间 DP

核心洞察:定义 dp[i][j] 表示 s[i..j] 是否为回文串。状态转移:若 s[i]==s[j] 且 s[i+1..j-1] 是回文,则 s[i..j] 也是回文。

DP 五步法:

  1. dp 定义dp[i][j] 表示 s[i:j+1] 是否为回文串
  2. 递推公式dp[i][j] = (s[i] == s[j] and (j - i <= 2 or dp[i+1][j-1]))
  3. 初始化dp[i][i] = True,长度 2 且相等时 dp[i][i+1] = True
  4. 遍历顺序:从下往上 i 递减,从左往右 j 递增(因为 dp[i][j] 依赖 dp[i+1][j-1])
  5. 举例验证:s=“babad” → i=3,j=4: s[3]=a,s[4]=d≠, 不成立;i=2,j=4: s[2]=b,s[4]=d≠; i=1,j=3: s[1]=a,s[3]=a=且 j-i=2 → dp[1][3]=T → “aba” ✓
def longestPalindrome(s):
    n = len(s)
    dp = [[False] * n for _ in range(n)]
    start = end = 0
 
    for i in range(n - 1, -1, -1):          # 从下往上
        for j in range(i, n):               # 从左往右
            if s[i] == s[j] and (j - i <= 2 or dp[i + 1][j - 1]):
                dp[i][j] = True
                if j - i > end - start:
                    start, end = i, j
 
    return s[start:end + 1]

思路三:Manacher 算法(O(n) 最优解)

利用回文半径数组和对称性,将时间复杂度降到 O(n)。面试一般不要求,但竞赛中很实用。

def longestPalindrome(s):
    # 预处理:插入分隔符,统一奇偶长度
    t = '^#' + '#'.join(s) + '#$'
    n = len(t)
    p = [0] * n            # p[i] 表示以 i 为中心的回文半径
    center = right = 0
 
    for i in range(1, n - 1):
        if i < right:
            p[i] = min(p[2 * center - i], right - i)
        # 中心扩展
        while t[i + p[i] + 1] == t[i - p[i] - 1]:
            p[i] += 1
        # 更新中心和右边界
        if i + p[i] > right:
            center, right = i, i + p[i]
 
    # 找到最大半径
    max_len, center_idx = max((p[i], i) for i in range(1, n - 1))
    start = (center_idx - max_len) // 2
    return s[start:start + max_len]

易错点

  • DP 遍历顺序:必须从下往上(i 递减),因为 dp[i][j] 依赖 dp[i+1][j-1]。从下往上保证左下角先被计算。
  • 中心扩展的数量:n 个字符有 2n-1 个中心(n 个单字符中心 + n-1 个双字符中心)。
  • 子串 vs 子序列:本题是子串(连续),区分于回文子序列问题。
  • 长度 1 的特殊情况:任何单字符都是回文,直接返回 s 本身。

框架提炼

中心扩展模板:

def expand_from_center(s):
    def expand(l, r):
        while l >= 0 and r < len(s) and s[l] == s[r]:
            l -= 1
            r += 1
        return l + 1, r - 1
 
    # 遍历所有中心
    for i in range(len(s)):
        expand(i, i)      # 奇数
        expand(i, i + 1)  # 偶数

区间 DP 模板(回文类):

def palindrome_interval_dp(s):
    n = len(s)
    dp = [[False] * n for _ in range(n)]
    for i in range(n - 1, -1, -1):
        for j in range(i, n):
            if s[i] == s[j] and (j - i <= 2 or dp[i + 1][j - 1]):
                dp[i][j] = True

关联题目