给你一个字符串 s
,请你将 s
分割成一些子串,使每个子串都是回文。
返回符合要求的 最少分割次数 。
示例1:
输入:s = "aab"
输出:1
解释:只需一次分割就可将 s 分割成 ["aa","b"] 这样两个回文子串。
示例2:
输入:s = "a"
输出:0
示例3:
输入:s = "ab"
输出:1
提示:
1 <= s.length <= 2000
s
仅由小写英文字母组成 此处利用和131.分割字符串类似的方法进行求解。
首先,用动态规划法判断所有的子字符串是否为回文串。
然后,用分支限界法对于字符串分割方式进行深度优先遍历,定义变量num用来表示分割次数,初始值为0。定义全局变量minnum表示当前已经搜索到的最小分割次数,其初始值为2000,因为字符串s最大长度为2000。
该题为求解最小值,需要确定每次搜索的下界,这里我设置的下界为当前分割次数num。
实现代码如下,空间复杂度O(n^ 2),动态规划时间复杂度O(n^2),分支限界法时间复杂度O(n . 2^n)。运行时间超时。
class Solution {
int n;
boolean[][] isPali;
int minnum = 2000;
int num = 0;
public int minCut(String s) {
n = s.length();
isPali = new boolean[n][n];
for (int i = 0; i < isPali.length; i++) {
Arrays.fill(isPali[i], true);
}
// 计算所有子字符串是否为回文串
for (int i = n - 1; i >= 0; i--) {
for (int j = i + 1; j < n; j++) {
isPali[i][j] = (s.charAt(i) == s.charAt(j)) && isPali[i+1][j-1];
}
}
// 计算最小分割次数
segmentation(0);
return minnum;
}
private void segmentation(int begin) {
if (begin == n) {
minnum = Integer.min(minnum, num - 1); //剩余子串不用分割,因此减1
}else {
for (int end = n - 1; end >= begin; end--) {
if (isPali[begin][end] && num < minnum) {
num++;
segmentation(end + 1);
num--;
}
}
}
}
}
为了降低时间复杂度,我们考虑对思路1进行进一步剪枝。
定义数组count,count[ i ]表示当搜索到下标 i 时,最小的分割次数。每次搜索时,与当前分割次数进行比较,如果当前分割次数已经大于count,则剪枝,如果小于,则更新count。
实现代码如下,空间复杂度O(n^ 2),动态规划时间复杂度O(n^2),分支限界法时间复杂度O(n . 2^n)。运行时间31 ms。
class Solution {
int n;
boolean[][] isPali;
int minnum = 2000;
int num = 0;
int[] count;
public int minCut(String s) {
n = s.length();
isPali = new boolean[n][n];
count = new int[n];
for (int i = 0; i < isPali.length; i++) {
count[i] = i + 1;
Arrays.fill(isPali[i], true);
}
// 计算所有子字符串是否为回文串
for (int i = n - 1; i >= 0; i--) {
for (int j = i + 1; j < n; j++) {
isPali[i][j] = (s.charAt(i) == s.charAt(j)) && isPali[i + 1][j - 1];
}
}
// 计算最小分割次数
segmentation(0);
return minnum;
}
private void segmentation(int begin) {
if (begin == n) {
minnum = Integer.min(minnum, num - 1); // 剩余子串不用分割,因此减1
} else if (num < count[begin]) {
count[begin] = num;
for (int end = n - 1; end >= begin; end--) {
if (isPali[begin][end]) {
num++;
segmentation(end + 1);
num--;
}
}
}
}
}
同样,先用动态规划法判断所有的子字符串是否为回文串。
然后,再次使用动态规划法计算最小分割次数。定义数组变量minnum,minnum[i] 表示从 s[0] 到 s[i] 的最小分割次数,且minnum[i] = min{ minnum[j] } + 1 , 0 < j < i,其中 s[j+1] 到 s[i] 为一个回文串。
minnum初始值为2000,因为字符串s最大长度为2000。
需注意,当 s 本身就是回文串时,minnum = 0;
实现代码如下,空间复杂度O(n^ 2),两次动态规划时间复杂度均为O(n^2)。运行时间24 ms。
class Solution {
int n;
boolean[][] isPali;
int minnum[];
int num = 0;
public int minCut(String s) {
n = s.length();
isPali = new boolean[n][n];
minnum = new int[n];
Arrays.fill(minnum, 2000);
for (int i = 0; i < isPali.length; i++) {
Arrays.fill(isPali[i], true);
}
// 计算所有子字符串是否为回文串
for (int i = n - 1; i >= 0; i--) {
for (int j = i + 1; j < n; j++) {
isPali[i][j] = (s.charAt(i) == s.charAt(j)) && isPali[i+1][j-1];
}
}
// 计算最小分割次数
for (int i = 0; i < n; i++) {
if (isPali[0][i]) {
minnum[i] = 0;
}else {
for (int j = 0; j < i; j++) {
if (isPali[j+1][i]) {
minnum[i] = Integer.min(minnum[i], minnum[j]+1);
}
}
}
}
return minnum[n-1];
}
}