LeetCode LCR 094. 分割回文串 II
题目描述


题意分析
把字符串切成若干个非空回文子串,求最少切割次数。切成
k段需要k - 1刀,因此整串已经回文时答案为零,不能把段数直接当成答案。每种划分都有一个最后的回文段。确定它的起点后,剩下的就是更短前缀的同类问题,所以可以枚举最后一段,递推最少刀数。
解法:回文表与最少切割 DP
核心思路
[!blue]
先预处理
pal[left][right],表示闭区间s[left..right]是否回文。两端字符必须相同;去掉两端后,内部区间也必须回文。因此条件是s[left] == s[right],并且right - left <= 2或pal[left + 1][right - 1]为真。长度不超过三时,内部为空或只有一个字符,两端相同就足够。这个短区间分支同时避免了访问不存在的内部下标。代码按右端点递增填表,依赖的
right - 1列已经完成,每个区间可以常数时间判定。再定义
dp[i]为把前缀s[0..i]切成回文段的最少刀数,初始化为i,对应每个字符单独成段。枚举最后一段的起点j,只考虑pal[j][i]为真的情况。若
j = 0,整个前缀就是回文,令dp[i] = 0;否则在j前切一刀,费用为dp[j - 1] + 1。对全部合法起点取最小值,既覆盖所有可能的最后一段,又保证其前缀使用最优划分,因此得到当前前缀的最少刀数。按
i递增计算时,所有用到的dp[j - 1]都已求出。最终dp[n - 1]对应整串。回文表让每次转移只需查表,避免反复逐字符判断回文。
解题步骤
- 分配回文表,按右端点从小到大、左端点不超过右端点的顺序计算。
- 依次处理前缀结尾
i,先令dp[i] = i作为合法上界。- 枚举最后一段的起点
j;不是回文就跳过,是回文则根据j是否为零更新最少刀数。- 返回
dp[n - 1]。单字符与整串回文都能由j = 0得到零次切割。
代码实现
class Solution {
public int minCut(String s) {
int n = s.length();
boolean[][] pal = new boolean[n][n];
for (int right = 0; right < n; right++) {
for (int left = 0; left <= right; left++) {
if (s.charAt(left) == s.charAt(right)
&& (right - left <= 2 || pal[left + 1][right - 1])) {
pal[left][right] = true;
}
}
}
int[] dp = new int[n];
for (int i = 0; i < n; i++) {
dp[i] = i;
for (int j = 0; j <= i; j++) {
if (!pal[j][i]) {
continue;
}
if (j == 0) {
dp[i] = 0;
} else {
dp[i] = Math.min(dp[i], dp[j - 1] + 1);
}
}
}
return dp[n - 1];
}
}
func minCut(s string) int {
n := len(s)
pal := make([][]bool, n)
for i := 0; i < n; i++ {
pal[i] = make([]bool, n)
}
for right := 0; right < n; right++ {
for left := 0; left <= right; left++ {
if s[left] == s[right] && (right-left <= 2 || pal[left+1][right-1]) {
pal[left][right] = true
}
}
}
dp := make([]int, n)
for i := 0; i < n; i++ {
dp[i] = i
for j := 0; j <= i; j++ {
if !pal[j][i] {
continue
}
if j == 0 {
dp[i] = 0
} else if dp[j-1]+1 < dp[i] {
dp[i] = dp[j-1] + 1
}
}
}
return dp[n-1]
}
复杂度分析
- 时间复杂度:$O(n^2)$,预处理回文区间与枚举前缀最后一段各需平方级时间。
- 空间复杂度:$O(n^2)$,主要是回文表,前缀最少刀数数组另占 $O(n)$。
关键点总结
[!green]
- 回文表判断最后一段是否合法,前缀 DP 负责选择最少刀数,两者职责不同。
- 最后一段起点为零时没有前缀,也不需要新增一刀。
- 回文表与刀数数组都要按依赖已经完成的顺序计算。
dp[i] = i是逐字符切分的有效方案,避免把默认零值误当成可行费用。
易错点总结
[!yellow]
- dp 记录切割次数而非段数;整个前缀已回文时需要 0 次切割。
- 前一段状态是 dp[j-1],j=0 时单独处理,不能访问负下标。
- 回文表按依赖顺序填写,长度为 1、2 的边界与一般转移区分清楚。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 131. 分割回文串 | 中等 | 回文子串判定相同,原题输出全部划分,本题只保留最少切割的前缀状态。 |
| 647. 回文子串 | 中等 | 回文区间是切割转移的前置条件,可复用中心扩展或区间DP的判定方式。 |
转载与许可
许可
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!