题目
给定一个字符串str,返回把str全部切成回文子串的最小分割数。
举例
str=“ABA”。本身是回文串,所以不需要切割,返回0。
str=“ACDCDCDAD”。最少需要切2次变成3个回文子串,比如“A”、“CDCDC”、“DAD”。返回2。
解答
经典动态规划。定义动态规划数组dp,dp[i]的含义是子串str[0…i]至少需要切割几次,才能把str[0…i]全部切割成回文串,所以dp[len-1]就是最终答案。
从左往右计算dp[i]的值,i初始为1(str[0]是单个字符,很显然是回文串,不需要切割,所以dp[0]=0),过程如下:
- 假设当前处在j位置(j<=i<len),如果str[j…i]是回文串,那么很显然dp[i]=dp[j-1]+1,意思是在str[0…i]这个子串上,str[j…i]已经是回文串了,那么它单独作为一个分割的部分,剩下的str[0…j-1]采用它最少的回文分割数,即dp[j-1]。
- 让j在0到i位置上枚举,所有符合情况中的最小值就是dp[i]的值,即dp[i] = min{dp[j-1]+1(0<=j<=i<len,且str[j…i]是回文串)}。
- 接下来判断str[j…i]是否是回文串,过程如下:
3.1 定义一个二维数组boolean[][] p,p[j][i]==true代表str[j…i]是回文串。
3.2 p[j][i]==true,一定是下面3种情况:
str[j…i]只有一个字符。
str[j…i]有2个字符并且这2个字符相等
str[j+1…i-1]是回文串,即p[j+1][i-1]==true,并且str[j]==str[i] - 在计算dp的过程中i是从左到右,j是从右到左遍历,所以p[j+1][i-1]一定已经计算过。
代码
public static int minCut(String str) {
if (str == null || str.length() == 0) {
return 0;
}
char[] cstr = str.toCharArray();
int len = cstr.length;
boolean[][] p = new boolean[len][len];
int[] dp = new int[len];
for (int i = 1; i < len; i++) {
dp[i] = Integer.MAX_VALUE;
for (int j = i; j >= 0; j--) {
if (cstr[i] == cstr[j] && (i - j < 2 || p[j + 1][i - 1] == true)) {
p[j][i] = true;
//如果j-1<0即j==0,说明整个str[0...i]是回文串,所以dp[i]是0
dp[i] = Math.min(dp[i], j - 1 >= 0 ? dp[j - 1] + 1 : 0);
}
}
}
return dp[len - 1];