(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。

posted on 2015-03-13 03:57  _icecream  阅读(159)  评论(0编辑  收藏  举报