5. 最长回文子串
LeetCode 51 · 中等 · 多维动态规划
题
给字符串 s,返回其中最长的回文子串(连续、正读反读一样)。有多个同长的,返回任意一个。
例:s = "babad" → "bab"("aba" 也对);s = "cbbd" → "bb"。
一句话
每个回文都有中心——单字符或两字符之间;枚举所有中心向两边扩,扩不动时记录长度。
关键技巧
区间 DP:状态是区间两端。
- 状态:\(f(i,j)\) =
s[i..j]是否回文。 - 转移:\(f(i,j) = (s_i = s_j) \wedge \big(j - i < 2 \vee f(i+1,j-1)\big)\)。
- 初值:长度 1 为真;长度 2 看两字符是否相等(已含在 \(j - i < 2\) 里)。
- 顺序:\(f(i,j)\) 依赖 \(f(i+1,j-1)\)——左下角,所以 \(i\) 从大到小、\(j\) 从小到大,或按区间长度递增。遍历顺序由依赖方向决定,这是区间 DP 最容易写错的地方。
中心扩展:同样的依赖,换个方向走。 DP 表里 \(f(i,j)\) 只依赖 \(f(i+1,j-1)\),依赖链是一条条从中心往外的斜线。沿斜线从中心出发往外走,一旦不等就停——后面的更长区间不可能是回文。每条斜线独立,所以不需要存表。
- 中心有 \(2n - 1\) 个:\(n\) 个单字符(奇数长)+ \(n - 1\) 个缝隙(偶数长)。
- 用
expand(l, r)统一两种:奇数传(i, i),偶数传(i, i+1)。
更快(知道即可)。 Manacher 算法利用已知回文的对称性复用半径,\(O(n)\)。
解
class Solution:
def longestPalindrome(self, s: str) -> str:
def expand(l: int, r: int) -> tuple[int, int]:
while l >= 0 and r < len(s) and s[l] == s[r]:
l, r = l - 1, r + 1
return l + 1, r # 回文是 s[l+1:r]
lo, hi = 0, 1
for i in range(len(s)):
for l, r in (expand(i, i), expand(i, i + 1)): # 奇、偶两种中心
if r - l > hi - lo:
lo, hi = l, r
return s[lo:hi]
时间 \(O(n^2)\),空间 \(O(1)\)。
class Solution2:
def longestPalindrome(self, s: str) -> str:
n = len(s)
f = [[False] * n for _ in range(n)]
lo, hi = 0, 1
for i in range(n - 1, -1, -1): # i 倒序:f[i][j] 依赖 f[i+1][j-1]
for j in range(i, n):
if s[i] == s[j] and (j - i < 2 or f[i + 1][j - 1]):
f[i][j] = True
if j + 1 - i > hi - lo:
lo, hi = i, j + 1
return s[lo:hi]
时间 \(O(n^2)\),空间 \(O(n^2)\)。提交时类名改回 Solution。
延伸
- 回文判定表复用:131. 分割回文串 先建同一张 \(f(i,j)\) 表,再回溯切分。
- 回文子序列(LeetCode 516):不要求连续,转移变成 \(s_i = s_j\) 时 \(f(i+1,j-1) + 2\),否则 \(\max(f(i+1,j), f(i,j-1))\)——等价于
s与reverse(s)的 1143. 最长公共子序列。 - 坑:别用「
s与反转串的最长公共子串」——"abacdfgdcaba"会得到不是回文的"abacd"。
自测
-
LeetCode 5,2026-09-29 读。 ↩