题目:给定一个字符串str,返回把str全部切成回文子串的最小分割数;
举例子:
str == “ABA” 返回0
str == “ACDCDCDAD" 返回2,最少切两次;“A”“CDCDC”“DAD”;切两下分成三个回文子串;
解题思路:
此题解题思路是这样的,假设给你一串字符"ACDFFDCE",我们可以直接看出此最少需要切两次,成三个子串"A"“CCFFCC”“E”,
可是我们也可以看出中间的子串"CCFFCC"还是可以切成三个回文子串的,可是我们要求最小切割数,所以切两次成三个回文子串
是最少的切割方法;
那么怎么实现呢?你怎么知道中间切成"CCFFCC"才会有最小的切割次数?
此时就要加一层循环来判断取得当前字符到 len-1 处整个子串的最少切割次数了;
举个例子:
ADCDEBE
- 首先两个指针i j 两次循环从后面开始遍历,dp数组dp[len]处的值为-1是有原因的, 就是从最末尾一个字符开始遍历的时候,E元素到len-1处的子串,最少切割次数为dp[6+1]+1=0; 刚好符合要求,后面的以此类推,设置p[6][6] =true,意思是下标6----6所在的子串就是回文串
- B元素的时候,最少切割次数为E的下一个字符所到 len-1处的子串最少切割次数加1,然后j从i开始遍历,判断B元素能不能跟后面子串中的那几个子串组成回文子串,循环结束后是没有,此时dp[5] =dp[6]+1=1;设置p[5][5]=true;
- i再次减一,此时继续j开始从i=4,第五个元素"E"处开始遍历,j=4时,dp[4] = dp[5]+1 =
2+1=2;j=5时,第五个元素"E"跟第六个元素"B"不等,所以他们直接不能组成回文;j继续遍历,j=6s时,发现下标为6的元素也是"E",然后此时再次判断这两者之间的子串是不是回文串,它两之间就是下标[5],之前的boolean元素已经判断过,p[5][5]=true,所以进入if语句,将当前的子串p[4][6]设置为true,代表4–6的子串就是个回文子串,然后求最小切割次数,就是下标为6元素的后面的子串的最少切割次数(上一步中已经求出) - 后面的依次类推,最后dp[0]的值就是最少切割次数;
下面说一下如何迅速判断子串是否为回文子串:
- 首先,一个元素就是一个回文子串
- 第二,如果两个元素下标相邻,并且相同,它两是个回文子串
- 第三,两个元素相同,但是长度大于2,假设下标分别为i和j,那么就判断i+1----j-1的子串是不是回文串,而我们在i从后向前遍历的过程中,每次碰到回文子串,都会用boolean二维数组存下来那个子串,所以此刻之间判断即可,如果此时boolean[i+1][j-1]不是true,那么当前i和j所在下标组成的子串不是回文串;
代码解答
/**
* @author Dangxuchao
* @Title: MinCut
* @ProjectName 第五章:字符串问题
* @Description: 回文最小分割数
*
* @date 2019/7/2 16:16
*/
public class MinCut {
public static void main(String[] args) {
String str1 = "ACDCDCE";
String str2 = "ABCDEFF";
String str3 = "AAAAABB";
String str4 = "AAAAAAA";
System.out.println(isMinCut(str1));
System.out.println(isMinCut(str2));
System.out.println(isMinCut(str3));
System.out.println(isMinCut(str4));
}
public static int isMinCut(String str){
//首先定义一个数组dp用来存放最小切割的次数;
char[] chars = str.toCharArray();
int len = chars.length;
int[] dp = new int[len+1];
//boolean一个二维数组;在计算dp数组的过程中,
// 可以迅速判断出子串是否为回文串;
//boolean数组的初始值为false;
boolean[][] p = new boolean[len][len];
dp[len] = -1;
//然后两个指针从后向前遍历,每次记录下能切割成最大回文子串的次数;
for (int i = len-1;i >= 0;i--){
/*每次进来将当前的dp[i]设置为最大值,
//内层循环结束后得出dp[i]的最小值,
这个最小值就是str串中下标i至len-1串的切成回文数的最小切割次数;*/
dp[i] = Integer.MAX_VALUE;
/*内层循环,就是说,从当前i所处的字符开始,
j一直遍历,因为i每次往前挪一个位置,就是减1,
减一之后,一开始的最少切割次数就是上次i内层循环
完之后得出来的最少切割次数+1,简单来说就是相当于把当前元素
直接单独切割出来;
然后往后遍历,如果能碰到哪个字符之后前后连接起来刚好凑成回文串
那么最少切割次数就是那个字符得下一个dp的值加1了,就是内层循环当前
计算出来的最少切割次数,
以此类推,最终dp[0]的值就是整个长串的最少回文分割数了;*/
for (int j = i;j < len;j++){
if (chars[i] == chars[j]&&(j-i<2 || p[i+1][j-1])){
//如果为true,那么代表子串从i到j的子串就是一个回文子串;
p[i][j] = true;
/*然后求最小分割次数,也就是j字符后面的子串的分割次数加上1,
因为从i到j是个回文子串
此处要取dp[i]和dp[j+1]+1之间的最小值*/
dp[i] = Math.min(dp[i],dp[j+1]+1);
}
}
}
return dp[0];
}
}
/*
输出结果:
2
5
1
0
*/