动态规划小结1

11 篇文章 0 订阅
4 篇文章 0 订阅

经典二维动态规划

二维动态规划很常见,笔试中最经常考。特点是随机选取一系列位置,即不连续

LCS(最长公共子序列问题,O(n^2))

题目描述:给两个字符串L和S,求出其中最长的公共子序列(可以不连续)
思路:这是典型的动态规划,有好几种变形。
毋庸置疑,当前状态肯定可以由上一状态得出。
楼主喜欢在动态规划中先确定i,j,dp[i][j]表示的含义
在这里是i表示使用L中到第i个字符串,j表示使用到S中第j个字符串,dp[i][j]即表示L用到第i个字符串,S用到第j个字符串时的最长上升子序列。
这里有个问题,就是在判断时,为了在i=0时保证i-1不越界,所以采用dp[i][j]是选用L[i-1]和S[j-1]

for(int i=0;i<=n;i++){
    dp[0][i] = 0;
    dp[i][0] = 0;
}
for(int i=1;i<=n;i++){
    for(int j=1;j<=n;j++){
        if(L[i-1]==S[j-1])
            dp[i][j] = dp[i-1][j-1] + 1;
        else
            dp[i][j] = max(dp[i-1][j],dp[i][j-1]);
    }
}

经典题解戳这里

变形1:最长回文子串。给出一串字符,去掉一些字符,使其构成最长的回文串

思路:回文子串,即reserve之后仍然相等(如abcba,颠倒后仍然是abcba,这是回文串的充分必要条件,即满足s.equals(s.reserve())这一条件的是回文串,回文串必须满足s.equals(s.reserve())),可以利用这个作为思路,将原字符L倒转成S串后,求LCS(因为回文串的充分必要条件,可以明白满足LCS的一定是回文串)

换个问法:最少插入多少个字符串,能使它成为回文子串。

变形2:最长上升子序列(可以非连续

说明,这里只是为了套用LCS的思想而想出的办法,还有其他解法。目前我遇到的最优解是O(n*logn);

在解决这个问题前先说明下最长连续上升子序列,这个不太像动态规划,因为他要求了连续,更像尺取法,O(n)遍历,左右两个点,left维护当前连续递增子序列中最小的点,right维护当前连续递增子序列中最右边的点,如果a[right] < a[right-1],left更新为right,right继续往前走。
接着说这个题的思路。

for(int i=1;i<n;i++){
    for(int j=0;j<i;j++){
        if(a[j]<a[i]){
            dp[i] = max(dp[j]+1,dp[i]);
        }
    }
    maxx = max(dp[i],maxx);
}       

另一个思路:这个题可以用最长公共子序列来做,将原数组排序一下,求其中LCS。因为排序时,最长上升子序列的相对位置没有改变。

复杂度也是n^2

其实此题还可继续优化,利用了辅助数组的思想,维持0-i的一个最长上升序列,如果a[i+1]>a[i],直接把a[i]添加在数组后面,如果a[i+1]<=a[i],采用二分查找的方式把a[i+1] 替换(注意,替换,不是插入) 到这个序列中(这里可能难理解的就是替换后为什么不影响后面的结果,这个正是这个算法的巧妙之处,多体会)。

详细题解看这里
以后有时间自己再总结下这个nlogn的算法思想吧
这个题还有个问法,就是最大上升子序列的和,代码很想,思路一模一样

memset(dp,0,sizeof(dp));
for(int i=1;i<n;i++){
    for(int j=0;j<i;j++){
        if(a[j]<a[i]){
            dp[i] = max(dp[j]+a[i],dp[i]);
        }
    }
    maxx = max(dp[i],maxx);
}       

应用

搜狐2017.9.17笔试题

题目大意:给你一串数字,问,怎样插入数字,使这串字符串成为回文字符串,而且和最小?
先不说这道题目,先来个过渡的。
题目大意如下:给你一串数字,求出最少插入多少个字符,可以使得这串数字成为回文串。
先说答案。采用求最长公共子序列的方法,假设原长度为n,求出最长回文串k,然后用n减去LCS的长度k,就是需要添加的最少字符。为什么?
这样想。如上面的变形1,如果我把这n-k个字符串拿掉了,剩下的就是最长上升子序列。那么放在这里,我再加上n-k个字符,一定可以构成回文串。所以,最少插入n-k个字符,可以使得这串数字成为回文串。
再回到这个题目,我们要求的不是最少插入多少个字符可以组成回文串了,而是在插入的字符满足回文串的条件下,插入的最小的值是多少。
我们可以采用同样的思路,换个方式来问,去掉一些数字使它成为回文串,去掉的数字的最小的和是多少?这就是求最大 上升子序列和 的值了。
这样列出来的动态方程是相似的,但是不是再求LCS了,因为LCS对应的是插入的数量最少,而不是插入的和最小。
那么,怎么求得最大上升回文子串和的值?

memset(dp,0,sizeof(dp));
for(int i=1;i<=n;i++){
    for(int j=1;j<=n;j++){
        if(a[i-1]==b[j-1]){
            dp[i][j] = dp[i-1][j-1]+a[i-1];
        }else{
            dp[i][j] = max(dp[i-1][j],dp[i][j-1]);
        }
    }
}
return dp[n][n];

求出最大上升回文子串的和,用2*sum-dp[n][n];就是答案,即sum+(sum-dp[n][n]),(sum-dp[n][n])的值就是需要加入到数组中的值。

下面给出一个例题,并给出相应的AC代码。
分割回文串 II leetcode 132
这个题目和上面不同,不是求最长字符串,而是问,把S分成若干个子串,每个子串都是回文串,且要求分的的子串尽量少。
先给出第一版的代码,超时:


    boolean[][] dp;
    public int minCut(String s) {
        int len = s.length();
        dp = new boolean[len][len];
        int[] sum = new int[len];
        for (int i = 0; i < len; i++) {
            sum[i] = i;
        }
        for (int i = 0; i < len; i++) {
            for (int j = 0; j < len; j++) {
                dp[i][j] = false;
            }
            dp[i][i] = true;
        }

        for (int i = 1; i < len; i++) {
            for (int j = 0; j <= i; j++) {
                if (isP(s, j, i)) {
                    if (j > 0) {
                        sum[i] = Math.min(sum[j - 1] + 1, sum[i]);
                    } else {
                        sum[i] = 0;
                    }
                } else {
                    if (j != 0)
                        sum[i] = Math.min(sum[j - 1] + i - j + 1, sum[i]);
                    else
                        sum[i] = Math.min(i - j + 1, sum[i]);
                }

            }
        }

        return sum[len - 1];
    }
    //检测是不是回文串
    public boolean isP(String s, int i, int j) {
        while (i < j) {
            char ch = s.charAt(i++);
            char ch2 = s.charAt(j--);
            if (ch != ch2)
                return false;
        }
        dp[i][j] = true;
        return true;
    }

这个代码超时了,超时的原因在于出现了aaaaaaaaaaaaaaaaaaaaaaaa……….等这种字符串。显然判断是否是回文串 isP() 这里耗时过长,有没有办法优化呢?
方法肯定是有的,因为每次判断是否是回文串,比如判断s[i]和s[j]时,那么s[i+1]和s[j-1]一定已经判断过了,就没必要再像上面那样每次都去循环来判断了。

    public boolean isP(String s, int i, int j) {
        if (i + 1 <= j - 1) {
            if (s.charAt(i) == s.charAt(j))
                return dp[i][j] = dp[i + 1][j - 1];
            return dp[i][j] = false;
        }
        return dp[i][j] = s.charAt(i) == s.charAt(j);
    }

思考:一个题目竟然可以有这么多的方式考,题型在变,但是题目的本质没有变,动态规划的思想都是灵魂。但是,这个动态规划又太宽泛了,太难列出方程,太难去理解和融汇贯通了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值