• 【palindrome partitioning II】cpp


    题目:

    Given a string s, partition s such that every substring of the partition is a palindrome.

    Return the minimum cuts needed for a palindrome partitioning of s.

    For example, given s = "aab",
    Return 1 since the palindrome partitioning ["aa","b"] could be produced using 1 cut.

    代码:

    class Solution {
    public:
            int minCut(string s)
            {
                const int len = s.size();
                vector<vector<bool> > p(len, vector<bool>(len,false));
                Solution::allPalindrome(p, s);
                vector<int> dp(len, INT_MAX);
                dp[0] = 0;
                for ( int i = 1; i < len; ++i )
                {
                    for ( int j = i; j >= 1; --j )
                    {
                        if ( j==1 )
                        {
                            if ( p[j-1][i] ) 
                            {
                                dp[i] = 0;
                                break;
                            }
                        }
                        if ( p[j][i] )
                        {
                            dp[i] = std::min(dp[i], dp[j-1]+1);
                        }
                    }
                }
                return dp[len-1];
            }
            // palindromes[i][j] denotes if s[i:j] is palindrome
            static void allPalindrome(vector<vector<bool> >& palindromes, string& s)
            {
                for ( int i = palindromes.size()-1; i >=0 ; --i )
                {
                    for ( int j = i; j <= palindromes.size()-1; ++j )
                    {
                        if ( (i+1>j-1 && s[i]==s[j]) || (i+1<=j-1 && s[i]==s[j] && palindromes[i+1][j-1]) )
                        {
                            palindromes[i][j] = true;
                        }
                    }
                }
            }
    };

    tips:

    通过此题掌握了动态规划的一些套路。

    动规的核心在于求解两个重要的变量

    p[i][j] : 代表s[i]到s[j]是否构成一个回文

    dp[i] : 标记s[0]到s[i]的最小回文分割

    接下来阐述上述两个变量的生成过程:

    1. p[i][j]

    正确地求出p[i][j]就是成功的一半,第一次没有AC主要就是p[i][j]求错了。在这里详细记录一下正确的求解过程

    第一步,要明确p[i][j]的含义,即s[i]到s[j]是否为回文

    第二步,把求解p[i][j]转化为状态转移方程,即p[i][j] = f(p[k][v]) (k<=i, v<=j, 且k==i,v==j不同时成立)。

    这个看起来是一个二维的dp方程,条件什么的比较麻烦,但实际上方程比较简单。

    p[i][j] = s[i]==s[j] && p[i+1][j-1]

    相当于两个条件

    a) 首尾相等

    b) 中间子字符串是回文

    当然,还要处理一些极端情况:

    极端情况1:i==j

    极端情况2:i==j+1

    把极端的case处理好,就可以开始dp过程了

    另,因为是回文(正反都一样)所以求解p的时候,只用求解上三角阵就可以了。

    2. dp[i]

    之前理解dp有一个误区:认为dp[i]由dp[i-1]一步推倒过来。这是非常不正确的。

    正确的理念应该是:dp[i]应该由dp[0]~dp[i-1]导出。(即当前结受到已计算出的中间结果影响,只不过有时候用不到回溯那么靠前的中间结果,只用回溯上一步的结果dp[i-1]就可以了

    然而,这道题正好不能只回溯一步,而是要回溯所有之前步骤。

    既然要回溯,就要有规则,规则要保证不重不漏。这里我们关注的点是s[i]:即,我们求dp[i]则s[i]是一定要包含在其中某个回文字串中的,具体如下:

    s[i]自己是一个回文子串

    s[i-1:i]构成回文子串

    s[i-2:i]构成回文子串

    ...

    s[0:i]构成回文串

    上述的i+1种情况如果都讨论到了,则dp[i]也就求出来了。

    具体阐述如下:

    s[0] ... s[i-2] s[i-1] s[i]:如果p[i-1][i]==true (即s[i-1]~s[i]是回文),则如果切割点在s[i-2]与s[i-1]之间,则dp[i] = min ( dp[i] , dp[i-2]+1)

    ....

    s[0] ... s[j-1] s[j] ... s[i]:如果p[j][i]==true,则如果切割点在dp[i] = min ( dp[i], dp[j-1]+1)

    s[0] ... s[i] :如果p[0][i]==true,则不用切割也可以,故最小切割数是0。

    按照上述的思路,再处理一下corner case,代码也就出来了。

    =====================================

    这份代码在处理corner cases上面做的不太好,有很多可以避免的冗余。但是先保留着吧,毕竟是原始的思考思路。第二遍刷的时候,再精细化一些corner case。

    ======================================

    第二次过这道题,看了第一次做的思路,代码重新写了一遍,比第一次过的时候要简洁一些。

    class Solution {
    public:
            int minCut(string s)
            {
                const int n = s.size();
                vector<vector<bool> > p(n, vector<bool>(n,false));
                Solution::isPalindrome(p, s);
                int dp[n];
                fill_n(&dp[0], n, 0);
                for ( int i=1; i<n; ++i )
                {
                    if ( p[0][i] ) 
                    {
                        dp[i]=0;
                        continue;
                    }
                    int cuts = INT_MAX;
                    for ( int j=i; j>0; --j)
                    {
                        if ( p[j][i] ) cuts = min(cuts, dp[j-1]+1);
                        if ( cuts==1 ) break;
                    }
                    dp[i] = cuts;
                }
                return dp[n-1];
            }
            static void isPalindrome(vector<vector<bool> >& p, string s)
            {
                for ( int i=0; i<s.size(); ++i )
                {
                    for ( int j=0; j<=i; ++j )
                    {
                        if ( i-j<2 )
                        {
                            p[j][i] = s[i]==s[j];
                        }
                        else
                        {
                            p[j][i] = s[i]==s[j] && p[j+1][i-1];
                        }
                    }
                }
            }
    
    };
  • 相关阅读:
    进程的描述
    前驱图和程序执行
    图形化编程娱乐于教,Kittenblock实例,小瓢虫找妈妈
    图形化编程娱乐于教,Kittenblock实例,猫抓老鼠使用扑鼠器
    图形化编程娱乐于教,Kittenblock实例,键盘控制猫抓老鼠
    图形化编程娱乐于教,Kittenblock实例,鼠标控猫抓老鼠
    图形化编程娱乐于教,Kittenblock实例,随机数猜测押宝
    图形化编程娱乐于教,Kittenblock实例,按钮控制动画
    图形化编程娱乐于教,Kittenblock实例,甲虫画出蜈蚣效果
    图形化编程娱乐于教,Kittenblock实例,图章的艺术
  • 原文地址:https://www.cnblogs.com/xbf9xbf/p/4544670.html
Copyright © 2020-2023  润新知