DFS超时!DP来帮忙

132. Palindrome Partitioning II

这道题考的是怎么用dp来解决dfs的问题
如果仅使用dfs,即使加了剪枝,这道题还是超时,这时候要思考能不能用其他办法,比如dp,观察一下每一步之间有没有关联,通过记录历史,来节省时间。

  • 怎么把前面的用到后面呢?
  • 在哪些地方有潜在的dp?

思考

  1. 最优的情况是自己就是回文,最差的情况是拆成单个字符
  2. 怎么利用以前的信息?从而得到最小分割?

思路


  1. 怎么通过前面的最小分割,来更新新加入的字符对应的最小分割?
    如果当前这个总的字符串是回文,自然结论为0分割;
    若不是:
    由于题目要求的是Minimum Cuts, 所以如果当前这个总的字符串不是回文,那我们要找到一个最小分割。
    也就是说,出现了新的字符之后,如果不能达到令整个字符串都变成回文的效果,但是这个新加入的字符和原字符串后面的一部分组成新的回文(s[j+1,i]),那此时对应的分割数就是cutNum[j]+1。
    这个查找过程可能会有多个匹配到的回文子串,我们选择最长的,使得cutNum[j]+1最小。
    那么,如果往前找,没有找到回文字符串怎么办?最差的情况是把当前新加入的字符单独作为一个回文,这样切割数为cutNum[i-1]+1

  2. 要怎么在字符串里面,快速判断子字符串是否回文?
    区间型动态规划
    对于判断某一段是否是回文串,使用区间型动态规划可以将时间复杂度从O(n)优化到O(1)
    使用一个二维boolean数组,来记录字符串 [i,j]是否回文。
    以后每次判断都可以借助于以前记录的信息。

实现


  1. 使用一个数组 cutNum, 记录字符串s长度为0~i 的最小分割数目

  2. 初始化cutNum, 如果字符串[0,i] 是回文,那么cutNum[i] = 0, 否则minCus[i] = i (代表最多分割 i 次)
    优化: 其实不需要*在这里进行初始化,因为后面DP的过程会一步一步计算出cutNum[i]

  3. 当 i > 1,进入dp, 每一次更新minCust[i] 的时候, 从j开始,j--往前面移动,不断判断字符串s[j,i]是不是回文。如果是的话,说明字符串[0,j-1]和字符串[j,i]可以分成两部分,而且[j,i]是回文。我们只需要 cutNum[j-1] + 1就代表当前这种分割的最小分割。
    在j继续往前移动直到 j ==0的过程中,还可能遇到另外的回文组合形式,这时候也是计算cutNum[j-1]+1
    把最小的cutNum[j-1]+1和cutNum[i]比较,取最小值更新cutNum[i]

  4. 那么,怎么样快速查询子字符串是否回文呢?
    使用空间动态dp
    大致的思路:
    matrix[i][j] == true 表示子字符串 s[i,j]是回文
    什么情况下可以判断得到matrix[i][j]==true呢?如何利用以前的信息?
    (1) matrix[j+1][i-1] == true && s[i] == s[j]
    matrix[j+1][i-1]==true 表示sub(j+1,i-1)是满足回文字符串
    或者
    (2) i-j < 2 && s[i] == s[j]
    如果 j - i == 1时,为相邻两个字符相等,
    如果 j - i == 0时,为同一个字符)


总结

根据遍历字符串的方向,可以分成正向、反向动态规划:
从前往后(正向dp) vs 从后往前(反向dp)

  1. 从前往后(正向dp)[前文分析的就是这种思路]

cutNum[i] 表示字符串s从0到i结尾的字串所需要的最小分割数。
如果从j到i为回文的话: cutNum[i] = min( cutNum[i] , cutNum[j] + 1)

相应的,这种方向判断字符串中任意两个字串是否回文的区间dp为:

使用dp[i][j], 为true则表示子字符串 s[j,i]是回文

(a) s[i] == s[j] && i-j < 2

(b) s[i]==s[j] && matrix[j+1][i-1] == true
时,matrix[i][j] = true
注意,(a) 必须放在(b)前面,否则程序运行到(b)会报越界错误。

具体代码:

     public int minCut(String s) {
       if(s==null || s.length() < 2)
           return 0;
       int[] cutNum = new int[s.length()];
       boolean[][] dp = new boolean[s.length()][s.length()];
       
       for(int i=0; i<s.length(); i++)
       {
           cutNum[i] = i;  // 最大切割数
           for(int j=i; j>=0; j--)
           {
               if( (s.charAt(i)==s.charAt(j)) && ( (i-j<2)||(dp[j+1][i-1]==true) ))
               {
                   dp[j][i] = true;
                   if(j==0)
                       cutNum[i] = 0;      // 字符串 从0到i已经是回文了,不需要切割
                   else if(cutNum[j-1] + 1  < cutNum[i])
                       cutNum[i] = cutNum[j-1]+1;
               }
           }
       }
       return cutNum[s.length()-1];
   }

注意: 在判断那里要先写 (i-j<2)再 || (dp[j+1][i-1]==true),否则会越界

2.从后往前(反向dp) 的思路

从字符串后半部分开始考虑:

cutNum[i] 表示字符串 s 从 i 到末尾的子串所需要的最小分割数
如果从 i 到 j 的子串为回文串的话,那么最小分割数就可能为 j + 1以后的子串的最小分割数加上 j 和 j + 1 之间的一割。
状态转移方程为:
cutNum[i] = min(cutNum[i], cutNum[j + 1] + 1)

相应的,这种方向判断字符串中任意两个字串是否回文的区间dp为:

使用dp[i][j], 为true则表示子字符串s[i,j]是回文

(a) s[i] == s[j] && i-j < 2 (字符为相邻字符或同一个位置的字符)

(b) s[i]]==s[j] && matrix[i+1][j-1] == true
时,matrix[i][j] = true;
注意,(a) 必须放在 (b)前面,否则程序运行到(b)会报越界错误

具体代码:

    public int minCut(String s) {
        if(s==null || s.length() < 2)
            return 0;
        int[] cutNum = new int[s.length()];
        boolean[][] dp = new boolean[s.length()][s.length()];
        
        for(int i=s.length()-1; i>=0; i--)
        {
            cutNum[i] = s.length()-i-1;
            for(int j=i; j<s.length(); j++)
            {
                if( (s.charAt(i) == s.charAt(j)) &&  ((j-i<2)||(dp[i+1][j-1]==true)) )
                {
                   dp[i][j] = true;
                   if(j==s.length()-1)
                        cutNum[i] = 0;
                   else
                        cutNum[i] = Math.min(cutNum[i], cutNum[j+1]+1);
                }
            }
        }
        return cutNum[0];
    }

参考:
http://blog.csdn.net/ljiabin/article/details/41173417


小收获

(1) 一个问题里面嵌套了两个dp的时候,要先找出大的dp, 然后找出小的dp
(2) dp的方向不一样的时候,要注意下标表示的是什么
(3) 多个循环判断条件放在一起的时候,要合理组合顺序

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335

推荐阅读更多精彩内容

  • 背景 一年多以前我在知乎上答了有关LeetCode的问题, 分享了一些自己做题目的经验。 张土汪:刷leetcod...
    土汪阅读 12,712评论 0 33
  • 分治方法 将问题划分成互不相交的子问题 递归地求解子问题 将子问题的解组合起来 动态规划(两个要素:最优子结构、子...
    superlj666阅读 489评论 0 0
  • 动态规划(Dynamic Programming) 本文包括: 动态规划定义 状态转移方程 动态规划算法步骤 最长...
    廖少少阅读 3,235评论 0 18
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,504评论 18 399
  • 计算机二级C语言上机题库(南开版) 1.m个人的成绩存放在score数组中,请编写函数fun,它的功能是:将低于平...
    MrSunbeam阅读 6,275评论 1 42