LeetCode 132:分割回文串 II
LeetCode 132:分割回文串 II
问题本质与核心挑战
给定字符串 s
,需将其分割为若干回文子串,求最少分割次数。核心挑战:
- 直接枚举所有分割方式(指数级复杂度)不可行;
- 需结合 动态规划 优化分割次数计算,并通过 回文预处理 加速判断。
核心思路:动态规划 + 回文预处理
1. 回文预处理(减少重复判断)
用二维数组 isPal[i][j]
记录 子串 s[i..j]
是否为回文,预处理后可 O(1)
查询:
- 单个字符:
isPal[i][i] = true
; - 两个字符:
isPal[i][i+1] = (s[i] == s[i+1])
; - 长度 ≥3:
isPal[i][j] = (s[i] == s[j] && isPal[i+1][j-1])
(依赖更短的子串结果)。
2. 动态规划定义与转移
- 状态定义:
dp[i]
表示 前i
个字符(s[0..i-1]
) 的最少分割次数。 - 初始条件:
dp[0] = -1
(空字符串的分割次数为-1
,方便后续计算);dp[i] = i-1
(最坏情况:每个字符单独分割,如"abc"
需要2
次分割)。
- 状态转移:
对于每个j
(前j
个字符),遍历所有可能的分割点i
(0 ≤ i < j
):- 若
s[i..j-1]
是回文(isPal[i][j-1] = true
),则dp[j] = min(dp[j], dp[i] + 1)
。
- 若
算法步骤详解(以示例 s = "aab"
为例)
步骤 1:预处理回文子串(isPal
数组)
子串范围 [i,j] | 长度 | 判断逻辑 | isPal[i][j] |
---|---|---|---|
[0,0] | 1 | 单个字符 | true |
[1,1] | 1 | 单个字符 | true |
[2,2] | 1 | 单个字符 | true |
[0,1] | 2 | s[0]='a' == s[1]='a' | true |
[1,2] | 2 | s[1]='a' ≠ s[2]='b' | false |
[0,2] | 3 | s[0]≠s[2] (直接不满足) | false |
步骤 2:初始化动态规划数组(dp
)
dp[0] = -1
(空字符串的分割次数);dp[1] = 0
(前1个字符"a"
,无需分割);dp[2] = 1
(初始值,后续会被更新);dp[3] = 2
(初始值,后续会被更新)。
步骤 3:状态转移计算
遍历 j
(前 j
个字符)和 i
(分割点):
j (前j 字符) | i (分割点) | 子串 s[i..j-1] | 是否回文(isPal[i][j-1] ) | dp[i] + 1 | dp[j] 更新后的值 |
---|---|---|---|---|---|
j=1 | i=0 | "a" | true | -1 + 1 = 0 | min(0, 0) = 0 |
j=2 | i=0 | "aa" | true | -1 + 1 = 0 | min(1, 0) = 0 |
j=2 | i=1 | "a" | true | 0 + 1 = 1 | min(0, 1) = 0 |
j=3 | i=0 | "aab" | false | - | 不更新 |
j=3 | i=1 | "ab" | false | - | 不更新 |
j=3 | i=2 | "b" | true | 0 + 1 = 1 | min(2, 1) = 1 |
完整代码(Java)
class Solution {public int minCut(String s) {int n = s.length();if (n == 0) return 0;// 步骤1:预处理回文子串boolean[][] isPal = new boolean[n][n];// 处理长度为1的回文for (int i = 0; i < n; i++) {isPal[i][i] = true;}// 处理长度为2的回文for (int i = 0; i < n - 1; i++) {isPal[i][i + 1] = (s.charAt(i) == s.charAt(i + 1));}// 处理长度≥3的回文for (int len = 3; len <= n; len++) {for (int i = 0; i + len <= n; i++) {int j = i + len - 1;isPal[i][j] = (s.charAt(i) == s.charAt(j) && isPal[i + 1][j - 1]);}}// 步骤2:动态规划int[] dp = new int[n + 1];// 初始条件:最坏情况,每个字符单独分割for (int i = 0; i <= n; i++) {dp[i] = i - 1;}// 状态转移:遍历前j个字符,尝试所有分割点ifor (int j = 1; j <= n; j++) {for (int i = 0; i < j; i++) {if (isPal[i][j - 1]) { // s[i..j-1]是回文dp[j] = Math.min(dp[j], dp[i] + 1);}}}return dp[n];}
}
关键逻辑解析
- 回文预处理:通过动态规划预处理所有子串的回文性,避免每次判断回文时重复计算,时间复杂度
O(n²)
。 - 动态规划状态:
dp[j]
表示前j
个字符的最少分割次数,利用已计算的dp[i]
快速推导,时间复杂度O(n²)
。 - 初始条件优化:
dp[0] = -1
是为了让dp[1]
的计算更自然(dp[0] + 1 = 0
,对应单个字符无需分割)。
该方法通过 预处理+动态规划 高效解决问题,时间复杂度为 O(n²)
,可处理题目中 n ≤ 2000
的规模。核心是将“回文判断”和“分割次数计算”解耦,通过预处理降低重复判断的开销,再利用动态规划的状态转移快速推导最优解。