一、题目
给你一个字符串 s
,请你将 s
分割成一些子串,使每个子串都是回文。
返回符合要求的 最少分割次数 。
示例 1:
输入:s = "aab"
输出:1
解释:只需一次分割就可将 s 分割成 ["aa","b"] 这样两个回文子串。
示例 2:
输入:s = "a"
输出:0
示例 3:
输入:s = "ab"
输出:1
提示:
1 <= s.length <= 2000
s
仅由小写英文字母组成
二、代码
class Solution {
public int minCut(String s) {
char[] str = s.toCharArray();
int n = str.length;
// 构造回文串检查预处理结构
boolean[][] check = createCheckMap(str, n);
// 从i出发(包括i位置)后面所有的字符串至少分成几个部分让每个部分都是回文。注意这里是存储的是分几部分,不是切几刀,求切几刀要再减1
int[] dp = new int[n + 1];
// 因为是从i到最后来设置的dp数组含义,所以i从n-1开始循环,i就表示从i出发尝试找到后面划分最少部分能保证都是回文串的分法。
for (int i = n - 1; i >= 0; i--) {
// 如果i~n-1范围整个就是一个回文串,就直接将dp[i]赋值为1
if (check[i][n - 1]) {
dp[i] = 1;
// 如果i~n-1范围不是回文串,就要去找怎么划分能让这一部分切出来的部分都是回文串
} else {
// 记录最少分出来的回文串数量
int next = Integer.MAX_VALUE;
// 尝试在i~n-1范围上所有可能的划分情况,就从前缀开始划分,看最少能划分出来多少回文串
// 此时dp[i+1...]肯定都已经求出来了,可以直接用
for (int j = i; j < n; j++) {
// 如果i~j范围是一个回文串,那么i~j就算是一个部分的回文串(是i~n-1范围上的一个前缀串),然后再加上j+1~n-1范围上最少能划分出来的回文串个数,就找到了一种尝试划分的方法,记录下这个回文串数量,然后从所有的可尝试选择中选出最少回文串数量的划分
if (check[i][j]) {
// 找到最少的回文串划分数量
next = Math.min(next, dp[j + 1]);
}
}
// 上面循环肯定会找到一个划分点j,能使得所有划分出来的回文串最少
// i~j肯定整个就是一个回文串,1个
// next记录的是j后面的部分能划分出来的最少回文串数量
// 所以i~n-1范围最少能划分出来的回文串数量就是next + 1
dp[i] = 1 + next;
}
}
// dp中存储的是最少回文串数,我们要求分割次数,所以要再减1
return dp[0] - 1;
}
// 构造回文串检查预处理结构
public boolean[][] createCheckMap(char[] str, int n) {
// check[i][j]:i~j范围的字符串是回文串就为true,不是回文串就是false
boolean[][] check = new boolean[n][n];
// 先给对角线赋值赋值,只有一个字符肯定都是回文串
for (int i = 0; i < n; i++) {
check[i][i] = true;
}
// 再给第二条对角线赋值,两个字符,如果两个字符相等,那么就是回文串,如果两个字符不等,就不是回文串
for (int i = 0; i < n - 1; i++) {
check[i][i + 1] = str[i] == str[i + 1];
}
// 没有i > j的情况,dp数组左下半部分无效
// 给普遍位置赋值
for (int i = n - 3; i >= 0; i--) {
for (int j = i + 2; j < n; j++) {
// 当str[i] == str[j],并且i+1~j-1范围还是一个回文串,就说明i~j范围是一个回文串
check[i][j] = str[i] == str[j] && check[i + 1][j - 1];
}
}
return check;
}
}
三、解题思路
尝试从i出发后面所有的字符串至少分成几个部分让每个部分都是回文的情况。这个还要提前构造一个检查任意范围是否是回文串的预处理结构。