(Version 1.0)
这道题是在Palindrome Partitioning的基础之上要求找一个最少分割次数的partitioning,因为之前已经做过了Palindrome Partitioning,所以最开始自然想到了用DP记录所有substring是否是palindrome,然后再对每个substring的mincuts用DP方法逐个由短至长求解,但是在LeetCode OJ上面run大数据时会超时,代码如下:
1 public class Solution { 2 public int minCut(String s) { 3 boolean[][] isPalindrome = new boolean[s.length()][s.length()]; 4 int[][] cuts = new int[s.length()][s.length()]; 5 for (int i = 0; i < isPalindrome.length; i++) { 6 isPalindrome[i][i] = true; 7 } 8 for (int l = 2; l <= s.length(); l++) { 9 for (int i = 0; i + l <= s.length(); i++) { 10 int j = i + l - 1; 11 isPalindrome[i][j] = (isPalindrome[i + 1][j - 1] || l == 2) && s.charAt(i) == s.charAt(j); 12 if (!isPalindrome[i][j]) { 13 cuts[i][j] = Integer.MAX_VALUE; 14 for (int k = i; k < j; k++) { 15 cuts[i][j] = Math.min(cuts[i][k] + cuts[k + 1][j] + 1, cuts[i][j]); 16 } 17 } 18 } 19 } 20 return cuts[0][s.length() - 1]; 21 } 22 }
看来需要对上面的代码进行优化,去除一些无用功。原题要求的是整个string的mincut,所以求起始位置不是最开头的substring的mincut其实对于问题的解决没有帮助,应该是可以省去的无用功,所以一个可能的优化是将最内层计算cuts数组元素的循环从计算isPalindrome数组的循环最内层拿出去。
优化最内层计算的核心思想是只考虑从头开始的substring的mincut,其状态的转移也只取决于所有比其短的从头开始的substring的mincut。也就是说,可以把上文提到的DP解法中的两层循环(实际是三层)减小为一层(实际是两层),因为我们现在只需要考虑起始点为输入string的起始的substring,然后每次循环再向末尾添上一个char就可以了。减少了一个变化的量(起始位置),循环层次也就减少了一层,代码如下:
// a solution from code ganker public int minCut(String s) { boolean[][] isPalindrome = new boolean[s.length()][s.length()]; int[] cuts = new int[s.length() + 1]; for (int i = 0; i < isPalindrome.length; i++) { isPalindrome[i][i] = true; } for (int l = 2; l <= s.length(); l++) { for (int i = 0; i + l <= s.length(); i++) { int j = i + l - 1; isPalindrome[i][j] = (isPalindrome[i + 1][j - 1] || l == 2) && s.charAt(i) == s.charAt(j); } } cuts[0] = -1; // pay attention here, cuts[0] must not be 0! for (int i = 0; i < s.length(); i++) { cuts[i + 1] = i; // use the fact that the substring of length i + 1 requires at most i cuts for (int j = 0; j <= i; j++) { if (isPalindrome[j][i]) { // if s[j:i] is not a palindrome, it doesn't affect the mincut cuts[i + 1] = Math.min(cuts[i + 1], cuts[j] + 1); } } } return cuts[s.length()]; }
这里从code ganker的解法(http://blog.csdn.net/linhuanmars/article/details/22837047)中学到了一个技巧,就是要在初始化DP的数组时充分利用现有问题的本身特性。那么这一题的问题背景有什么特性呢?就是一个string所需要的mincut数最大不超过其长度减一,也就是说,极端情况也就是string中最长的palindrome长度为1,这就是为什么代码中初始化DP数组的元素时要给其初始化为所代表的元素index - 1。DP的状态转移描述如下:cuts[i]已经被初始化为最大的可能cut数,所以接下来要做的其实是尝试减小这个值。什么情况下这个值有可能被减小呢?就是在这个substring的substring中有palindrome时。而在这个一维DP中,我们保存的是从输入string头到某一中间位置的substring的mincut,所以内层循环所需要判断,或者说需要处理的当前子问题,就是判断从没被存储的位置开始到当前substring结束这一部分是不是palindrome。