LeetCode算法日记 - Day 96: 最长回文子串
目录
1. 最长回文子串
1.1 题目解析
1.2 解法
1.3 代码实现
1. 最长回文子串
https://leetcode.cn/problems/longest-palindromic-substring/description/
给你一个字符串 s,找到 s 中最长的 回文 子串。
示例 1:
输入:s = "babad" 输出:"bab" 解释:"aba" 同样是符合题意的答案。
示例 2:
输入:s = "cbbd" 输出:"bb"
提示:
1 <= s.length <= 1000s仅由数字和英文字母组成
1.1 题目解析
题目本质
这是一个子串搜索问题,要在字符串中找出最长的回文子串。本质上需要判断所有可能子串的回文性质,并记录最长的那个。核心挑战是"如何高效判断并找出最长回文"。
常规解法
最直观的想法:枚举所有可能的子串,对每个子串都进行回文判断,记录最长的。
// 常规解法:暴力枚举(可能超时)static class BruteForceSolution {public String longestPalindrome(String s) {int n = s.length();String longest = "";// 枚举所有子串for (int i = 0; i < n; i++) {for (int j = i; j < n; j++) {// 提取子串并判断是否为回文String sub = s.substring(i, j + 1);if (isPalindrome(sub) && sub.length() > longest.length()) {longest = sub;}}}return longest;}// 判断字符串是否为回文private boolean isPalindrome(String str) {int left = 0, right = str.length() - 1;while (left < right) {if (str.charAt(left) != str.charAt(right)) {return false;}left++;right--;}return true;}}
问题分析
暴力枚举的复杂度是 O(n³),当 n = 1000 时,10³ × 10³ × 10³ = 10⁹ 次操作,会超时。
关键观察:大回文串的判断依赖于小回文串
-
判断 "abcba" 是否为回文时,需要先确认 "bcb" 是回文
-
判断 "bcb" 是否为回文时,又需要确认 "c" 是回文
-
存在大量重复判断,可以优化
思路转折
要想高效 → 必须避免重复判断 → 动态规划预处理所有子串
核心洞察:回文串的递推性质
-
如果 s[i] == s[j],且 s[i+1..j-1] 是回文,那么 s[i..j] 也是回文
-
可以从小区间推导到大区间
-
预先计算所有子串的回文性质,顺便记录最长的
状态定义:dp[i][j] = s[i..j] 是否为回文
1.2 解法
算法思想
采用区间动态规划:
1. 定义 dp[i][j] = s[i..j] 是否为回文子串
2. 状态转移:
- 单字符:dp[i][i] = true
- 相邻字符:dp[i][i+1] = (s[i] == s[i+1])
- 其他情况:dp[i][j] = (s[i] == s[j]) && dp[i+1][j-1]
3. 遍历顺序:i 从大到小(n-1 → 0),j 从小到大(i → n-1)
4. 过程中记录最长回文的起始位置和长度
步骤拆解
i)边界处理:长度小于2直接返回
ii)初始化:创建 boolean[][] dp,初始 begin=0, len=1
iii)填表:
-
外层循环:i 从 n-1 倒序到 0
-
内层循环:j 从 i 正序到 n-1
-
判断 s[i] 是否等于 s[j]:
-
相等 + 单字符(i==j)→ dp[i][j] = true
-
相等 + 相邻(i+1==j)→ dp[i][j] = true
-
相等 + 其他 → dp[i][j] = dp[i+1][j-1]
-
不相等 → 保持 false
-
-
若 dp[i][j] 为真且长度更长,更新 begin 和 len
iv)返回结果:s.substring(begin, begin+len)
易错点
-
遍历顺序错误:必须 i 倒序(i-- 而不是 i++),因为 dp[i][j] 依赖 dp[i+1][j-1]。如果写成 i++ 会导致无限循环
-
长度计算错误:区间 [i, j] 的长度是 j-i+1,不是 i-j+1。因为 j >= i(j在右边),写反会得到负数或0
-
begin 更新遗漏:找到更长回文时,必须同时更新 begin 和 len,否则起始位置和长度不匹配,导致提取错误的子串
-
边界条件处理:相邻字符判断应该是 i+1 == j 或 j-i == 1,不要写成 i == j-1(虽然等价但容易混淆)。在这种情况下不能直接访问 dp[i+1][j-1],否则会越界
1.3 代码实现
// 最长回文子串
static class Solution {public String longestPalindrome(String s) {int n = s.length();if (n < 2) return s; // 边界处理char[] ch = s.toCharArray();boolean[][] dp = new boolean[n][n];int begin = 0, len = 1; // 初始化:至少有单字符// 填表:i 倒序,j 正序for (int i = n-1; i >= 0; i--) {for (int j = i; j < n; j++) {if (ch[i] == ch[j]) {// 首尾字符相等if (i == j) {dp[i][j] = true; // 单字符} else if (i+1 == j) {dp[i][j] = true; // 相邻字符} else {dp[i][j] = dp[i+1][j-1]; // 看内部}}// 首尾不等,保持 false// 更新最长回文if (dp[i][j] && j-i+1 > len) {begin = i;len = j-i+1;}}}return s.substring(begin, begin + len);}
}
复杂度分析
-
时间复杂度:O(n²)
-
空间复杂度:O(n²)
