LeetCode算法日记 - Day 95: 回文子串
目录
1. 回文子串
1.1 题目解析
1.2 解法
1.3 代码实现
1. 回文子串
https://leetcode.cn/problems/palindromic-substrings/description/
给你一个字符串 s ,请你统计并返回这个字符串中 回文子串 的数目。
回文字符串 是正着读和倒过来读一样的字符串。
子字符串 是字符串中的由连续字符组成的一个序列。
示例 1:
输入:s = "abc" 输出:3 解释:三个回文子串: "a", "b", "c"
示例 2:
输入:s = "aaa" 输出:6 解释:6个回文子串: "a", "a", "a", "aa", "aa", "aaa"
提示:
1 <= s.length <= 1000s由小写英文字母组成
1.1 题目解析
题目本质
这是一个子串统计问题,要统计一个字符串中所有回文子串的数量。本质上是判断每个可能的子串是否为回文,然后累加计数。核心是"如何高效判断所有子串的回文性质"。
常规解法
最直观的想法:枚举所有可能的子串,对每个子串都进行回文判断。
// 常规解法:暴力枚举(可能超时)static class BruteForceSolution {public int countSubstrings(String s) {int n = s.length();int count = 0;// 枚举所有可能的子串for (int i = 0; i < n; i++) {for (int j = i; j < n; j++) {// 判断 s[i..j] 是否为回文if (isPalindrome(s, i, j)) {count++;}}}return count;}// 判断 s[left..right] 是否为回文private boolean isPalindrome(String s, int left, int right) {while (left < right) {if (s.charAt(left) != s.charAt(right)) {return false;}left++;right--;}return true;}}
问题分析
暴力枚举的复杂度是 O(n³):当 n = 1000 时,1000³ = 10⁹ 次操作,可能会超时。
关键观察:回文判断存在大量重复计算
-
判断 "abcba" 是否为回文时,会检查 "bcb"
-
之后判断 "bcb" 是否为回文时,又要重新检查一遍
-
这种重复计算可以被优化!
思路转折
要想高效 → 必须避免重复判断 → 动态规划预处理
核心洞察:大回文串依赖于小回文串
-
如果 "aba" 是回文,那么只需要看首尾字符是否相等
-
如果 s[i] == s[j],且 s[i+1..j-1] 是回文,那么 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. 统计所有 dp[i][j] == true 的数量
步骤拆解
i)初始化:创建 boolean[][] dp,默认全为 false
ii)填表:
-
外层循环:i 从 n-1 倒序到 0
-
内层循环:j 从 i 正序到 n-1
-
判断 s[i] 是否等于 s[j]:
-
相等 + 单字符 → dp[i][j] = true
-
相等 + 相邻 → dp[i][j] = true
-
相等 + 其他 → dp[i][j] = dp[i+1][j-1]
-
不相等 → 保持 false
-
iii)统计:遍历 dp 数组,累加所有 true 的个数
易错点
-
遍历顺序错误:必须 i 倒序(从大到小),因为 dp[i][j] 依赖 dp[i+1][j-1],需要先计算大的 i。如果 i 正序,会访问到未计算的状态
-
统计时遍历范围错误:不能遍历整个 n×n 矩阵,因为 j < i 的部分没有意义。应该只遍历上三角(j ≥ i),或者统计时也用双重循环 for(i) for(j=i to n-1)
-
边界条件遗漏:忘记处理单字符(i==j)和相邻字符(i+1==j)的情况,直接访问 dp[i+1][j-1] 会越界或逻辑错误
-
首尾不等时未处理:当 s[i] != s[j] 时,应该保持 dp[i][j] = false,不需要显式赋值(Java默认false)
1.3 代码实现
// 回文子串的数目
static class Solution {public int countSubstrings(String s) {int n = s.length();char[] nums = s.toCharArray();boolean[][] dp = new boolean[n][n];// 填表:i 倒序,j 正序for (int i = n-1; i >= 0; i--) {for (int j = i; j < n; j++) {if (nums[i] == nums[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}}// 统计回文子串数量int ret = 0;for (int i = 0; i < n; i++) {for (int j = i; j < n; j++) { // 只遍历上三角if (dp[i][j]) ret++;}}return ret;}
}
复杂度分析
-
时间复杂度:O(n²)
-
空间复杂度:O(n²)
