跳到正文
第 5 题中等

最长回文子串

Longest Palindromic Substring

求字符串中最长的回文子串;子串必须连续。

在力扣中练习

题目描述

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

 

示例 1:

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

示例 2:

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

 

提示:

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

解题思路

主解法

区间 DP · 首尾向内

首尾字符相同,且内部子串是回文,整体才是回文

暴力枚举所有子串、再逐段判断回文是 O(n³)。用 dp[i][j] 表示 s[i..j] 是否为回文,就能把判断压成一次转移:

dp[i][j] = (s[i] == s[j]) && (j - i < 2 || dp[i+1][j-1])

也就是说:首尾字符必须相同;长度不超过 2 时直接成立,更长时还要看内部区间。

自拟主例 s = "abac"。长度 2 的区间首尾都不同,全部为 F;长度 3 的 "aba" 首尾都是 a,内部 "b" 是回文,于是成立;长度 4 的 "abac" 首尾 b/c 不同,直接失败。

图解
最长回文子串 · 长度 2写入答案

dp[i][j]:s[i..j] 是否为回文;按区间长度从小到大填表,内部区间先确定

i \ jabacabacT初dp[0][0]:TFdp[0][1]:F·dp[0][2]:尚未计算·dp[0][3]:尚未计算·dp[1][0]:·T初dp[1][1]:TFdp[1][2]:F·dp[1][3]:尚未计算·dp[2][0]:··dp[2][1]:·T初dp[2][2]:TF≠dp[2][3]:F,当前目标·dp[3][0]:··dp[3][1]:··dp[3][2]:·T初dp[3][3]:T
dp[2][3]=F
未计算下三角不适用初值
长度 2 的首尾都不同,全部为 F。

填表顺序必须按区间长度从小到大:计算长度 L 时,内部长度 L-2 的区间已经确定。如果按 i、j 双重循环随便填,dp[i+1][j-1] 可能还没算。

分步图解

"aba" 首尾都是 'a',内部 "b" 也是回文,所以是回文;当前最长 aba。

L
3
最长
aba
最长回文子串 · 长度 3写入答案

dp[i][j]:s[i..j] 是否为回文;按区间长度从小到大填表,内部区间先确定

i \ jabacabacT初dp[0][0]:TFdp[0][1]:FT=dp[0][2]:T,当前目标·dp[0][3]:尚未计算·dp[1][0]:·T初dp[1][1]:T,选中的来源Fdp[1][2]:F·dp[1][3]:尚未计算·dp[2][0]:··dp[2][1]:·T初dp[2][2]:TFdp[2][3]:F·dp[3][0]:··dp[3][1]:··dp[3][2]:·T初dp[3][3]:Tdp[1][1] → dp[0][2]:内部子串
dp[0][2]=dp[1][1]
=T=T
未计算下三角不适用初值
1 / 3
长度 3 的 aba 成立;长度 4 的 abac 首尾不同,不再扩展。

边界是对角线 dp[i][i] = true。答案在填表过程中用 start/len 记录,时间 O(n²),空间 O(n²)。

时间 O(n²)空间 O(n²)

易错点

  • 按 i、j 从小到大填表:dp[i+1][j-1] 还没计算,内部区间读到默认 false。
  • 长度 2 也去读 dp[i+1][j-1]:此时内部是空区间,i+1 > j-1,需要单独处理。
  • 首尾相同时直接返回 true:内部区间可能并不是回文,必须继续检查 dp[i+1][j-1]。
对照解法

中心扩展 · O(1) 空间

回文从中心向两边对称,枚举中心即可

时间同为 O(n²),但只用了常数空间,也不需要维护二维表;代价是每次扩展可能重复扫描同一段字符。

回文串关于中心对称。中心有两种:

  • 奇数长度:中心是一个字符,例如 "bab" 的中心是 a;
  • 偶数长度:中心是两个字符之间的空隙,例如 "abba" 的中心在两个 b 之间。

对每个中心向左右扩展,直到字符不同或越界,就得到以它为中心的最长回文。共有 2n-1 个中心,每个中心最多扩展 O(n),总时间 O(n²),空间 O(1)。

实现时用 expand(i, i) 处理奇数中心,expand(i, i+1) 处理偶数中心。扩展结束后,r - l - 1 就是回文长度,起点是 l + 1。

  1. 1start = 0,len = 1。
  2. 2对每个 i,分别调用 expand(i, i) 和 expand(i, i+1)。
  3. 3向两边扩展直到字符不同或越界。
  4. 4长度更大时更新 start/len。
  5. 5返回 s.substr(start, len)。
时间 O(n²)空间 O(1)

易错点

  • 只扩展奇数中心:偶数长度的回文会被漏掉。
  • 扩展结束后起点写成 l:循环退出时 l 已经多减了 1,起点应为 l+1。
  • 长度相同也更新:会改变先找到的答案,虽然仍是正确回文,但结果不稳定。

代码与演示

主解法配有图解;对照解法用于比较思路、复杂度或实现方式。

演示输入s = "abac"

单个字符都是回文:对角线全部设为 true,长度 1。

s
abac
最长回文子串 · 单字符初值建立初值

dp[i][j]:s[i..j] 是否为回文;按区间长度从小到大填表,内部区间先确定

i \ jabacabacT初dp[0][0]:T,当前目标·dp[0][1]:尚未计算·dp[0][2]:尚未计算·dp[0][3]:尚未计算·dp[1][0]:·T初dp[1][1]:T·dp[1][2]:尚未计算·dp[1][3]:尚未计算·dp[2][0]:··dp[2][1]:·T初dp[2][2]:T·dp[2][3]:尚未计算·dp[3][0]:··dp[3][1]:··dp[3][2]:·T初dp[3][3]:T
dp[0][0]=T
未计算下三角不适用初值
1 / 8
查看 C++ 代码

延伸阅读

  • 区间 DP 的填表顺序由依赖方向决定:依赖更短区间,就按长度递增。
  • 最长回文子序列(516)不要求连续,状态定义相似,但转移在首尾不同时取两边的最大值。