文章目录
题目链接 :
LeetCode - Longest Palindromic Substring
一、题目描述
给定一个字符串 s,请你找出其中的 最长回文子串 。
回文子串指的是正着读和反着读都相同的字符串片段。
示例:
输入: s = "babad"
输出: "bab"
解释: "aba" 同样是有效答案
输入: s = "cbbd"
输出: "bb"
二、问题分析
回文的特征是 对称,因此求解过程的核心就是如何找到字符串中的对称中心并向两边扩展。
下图从思维层面展示了几种常见算法思路:
最长回文子串
暴力枚举
检查所有子串是否回文
时间复杂度 O(n³)
动态规划
状态定义 dp[i][j]
转移方程依据 s[i]==s[j]
时间复杂度 O(n²)
中心扩展
每个字符为中心
向两边扩展计算长度
时间复杂度 O(n²)
Manacher算法
利用镜像特性
线性时间求解 O(n)
三、解法一:暴力枚举
原理
- 枚举所有可能的子串。
- 对每个子串判断是否为回文。
- 记录最大长度的回文子串。
流程图
是
否
输入字符串 s
枚举所有子串 s[i:j]
判断是否为回文
是回文?
更新最长长度与起止下标
继续下一子串
输出最长回文子串
时间复杂度
- 枚举子串次数约为 O(n²)
- 判定回文 O(n)
- 总时间复杂度:O(n³)
- 空间复杂度:O(1)
属于理论上最简单但效率最低的方式,适合初学者理解。
四、解法二:动态规划(DP)
原理
使用二维数组 dp[i][j] 表示 s[i...j] 是否为回文字符串。
- 当
s[i] == s[j]时,若dp[i+1][j-1]为真,则dp[i][j]也为真。 - 边界情况:单个字符必为回文 (
dp[i][i] = true)
状态转移方程:
d p [ i ] [ j ] = ( s [ i ] = = s [ j ] ) & & ( j − i < 3 ∥ d p [ i + 1 ] [ j − 1 ] ) dp[i][j] = (s[i] == s[j]) \ \&\& \ (j - i < 3 \ \| \ dp[i+1][j-1]) dp[i][j]=(s[i]==s[j]) && (j−i<3 ∥ dp[i+1][j−1])
伪逻辑时序
状态表 dp[i][j] 内层循环 j ← i 到 n-1 外层循环 i ← n-1 到 0 状态表 dp[i][j] 内层循环 j ← i 到 n-1 外层循环 i ← n-1 到 0 逐步扩大区间 [i...j] 判断 s[i]==s[j] 且区间内部为回文 若成立则更新最大长度
时间复杂度
- 状态数量 O(n²)
- 每次转移 O(1)
- 总复杂度:O(n²)
- 空间复杂度:O(n²)
Java 实现
java
public class Solution {
public String longestPalindrome(String s) {
int n = s.length();
boolean[][] dp = new boolean[n][n];
String res = "";
for (int i = n - 1; i >= 0; i--) {
for (int j = i; j < n; j++) {
if (s.charAt(i) == s.charAt(j) && (j - i < 3 || dp[i + 1][j - 1])) {
dp[i][j] = true;
}
if (dp[i][j] && (j - i + 1) > res.length()) {
res = s.substring(i, j + 1);
}
}
}
return res;
}
}
五、解法三:中心扩展法
原理
回文子串的"中心"可以是:
- 一个字符(奇数长度回文)
- 两个相邻字符(偶数长度回文)
从每个中心向两侧扩展,直到不再匹配。
示意图
中心 i/i+1
左边扩展 →
← 右边扩展
判断 s[left]==s[right]
更新最长回文长度
时间复杂度
- 中心共有 2n-1 个。
- 每次扩展最多 O(n)
- 总复杂度:O(n²)
- 空间复杂度:O(1)
Java 实现
java
public class Solution {
public String longestPalindrome(String s) {
if (s == null || s.length() < 2) return s;
int start = 0, maxLen = 1;
for (int i = 0; i < s.length(); i++) {
int len1 = expandFromCenter(s, i, i);
int len2 = expandFromCenter(s, i, i + 1);
int len = Math.max(len1, len2);
if (len > maxLen) {
maxLen = len;
start = i - (len - 1) / 2;
}
}
return s.substring(start, start + maxLen);
}
private int expandFromCenter(String s, int left, int right) {
while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)) {
left--;
right++;
}
return right - left - 1;
}
}
该方法是实际工程中最常见的实现,兼顾可读性与性能。
不同算法对比总结
| 方法 | 时间复杂度 | 空间复杂度 | 思想 | 优势 | 劣势 |
|---|---|---|---|---|---|
| 暴力枚举 | O(n³) | O(1) | 完全枚举 | 易理解 | 极慢 |
| 动态规划 | O(n²) | O(n²) | 状态转移 | 稳定性高 | 占用空间多 |
| 中心扩展 | O(n²) | O(1) | 对称扩展 | 简洁高效 | 理论非最优 |
如果是初学者,推荐使用中心扩展法,代码简洁、性能足够。