leetcode中的DP题目总结

[leetcode 413]Arithmetic Slices

dp解释

dp[i] 代表以 i 结尾的arithmetic slice的个数。因此有下列关系
dp[i]=dp[i1]+1,ifA[i]A[i1]=A[i1]A[i2]
dp[i]=0,ifA[i]A[i1]A[i1]A[i2]

注意

  • dp中经常会设计有以i结尾的**的个数

程序

public int numberOfArithmeticSlices(int[] A) {
        int len  = A.length;
        if(len < 3) return 0;

        int result = 0;        
        int[] dp = new int[len];

        for(int i = 1; i < len-1; i++) {
            if(A[i+1] - A[i] == A[i] - A[i-1]) {
                dp[i+1] = dp[i] + 1;
                result += dp[i+1];
            } else {
                dp[i+1] = 0;
            }

        }

        return result;        
    }

[leetcode 375] Guess Number Higher or Lower II

题目

给一个范围n,对方心里想了某个数字(范围在[1,n],具体大小你并不知道)。你每次猜一个数字,对方会告诉你猜得大了还是猜的小了或者猜对了。如果这次没有猜对,你需要支付你猜的这个数字的大小。问,给定一个范围n时,最少支付多少才能保证一定猜到这个数字呢?

解释

这个问题有点绕,然而却是一个典型的minmax问题。可以先从简单的例子来想。
在一个范围[m,n]中猜数字,
1. 如果只有一个数,比如[5,5],那么一次就会猜中,就不用支付,因此最少支付0。
2. 如果范围里有两个数,比如[5,6],你猜5如果错了,你就知道是6了,需要支付5;你猜6如果错了,同样一定会知道答案,但是需要支付6。相比之下,支付5就可以一定猜出来。
3. 如果范围里有3个数,比如[5,6,7],你猜5,那么[6,7]中猜6一定知道答案,共支付11;你猜6,[5,5]和[7,7]不用支付就可以知道答案,共支付6;你猜7,那么[5,6]需要支付5,共支付12;比较11,6,12可知,最少支付为6。
4. 一般的情况,我们不管范围里有几个数,用 dp[i][j] 来表示在 [i,j] 范围内猜数字,最难猜到的数字让我们支付了多少(最少支付了多少可以保证猜到这个范围内的任何数字)。这相当于,如果我们猜任何一个 k[i,j] ,一定会得到需要猜的数字的是在 [i,k1] 这个范围还是在 [k+1,j] 这个范围,相应再去找 dp[i][k1] , dp[k+1][j] 来看这两个范围的保底花费。由于我们并不知道这个数字在哪个范围,因此要取最大的来保证我们一定可以猜出来最难猜的那个数字。接下来就是dp的递推式了

dp[i][j]=min(dp[i][j],max(dp[i][k1],dp[k+1][j])),k[i,j]

注意

大范围需要先知道小范围,用什么顺序来求dp数组呢?如下图:
leetcode中的DP题目总结_第1张图片

程序

    public int getMoneyAmount(int n) {
        int dp[][] = new int[n+1][n+1];

        for(int j = 1; j <= n; j++) {
            for(int i = 1; i + j <= n; i++) {

                dp[i][i+j] = Integer.MAX_VALUE;

                for(int k = i; k <= i+j; k++) {
                    dp[i][i+j] = Math.min(dp[i][i+j], k + Math.max(dp[i][k-1], (k+1 > i+j)?0:dp[k+1][i+j]));
                }
            }
        }
        return dp[1][n];

    }

[leetcode 115] Distinct Subsequences

题目

给定两个字符串S,T。S中有多少个不同的子序列恰好等于T
这个题目很容易理解错,题目想问的并不是S和T有多少个不同的公共子序列。举一个例子
S=”rabbbit”, T=”rabbit”,则返回3。原因是:
S的三个子序列rab-bit , ra-bbit,rabb-it恰好等于T,因此返回3。

DP解释

i[1,S.len] j[1,T.len] 分别代表当前指向S和T的位置。用 dp[j][i] 代表S的前 i 个字母组成的子串中有多少个子序列恰好等于T的前 j 个字母组成的子串。则有,
S[i]=T[j] , dp[j][i]=dp[j1][i1]+dp[j][i1]
S[i]T[j] , dp[j][i]=dp[j][i1]
以上的递推式的意思是:
1. S增加一个字母,但是和T的当前字母不相等时,S不会增加新的子序列和T相等,因此 dp[j][i]=dp[j][i1]
2. S增加一个字母,和T的当前字母相等时,S会增加 dp[j1][i1] 个子序列和T相等(任何一个相等的情况在末尾加上同一个字母依然符合条件),再计算上之前的序列得到 dp[j][i]=dp[j1][i1]+dp[j][i1]

注意

  1. 计算的顺序是一行一行算
  2. 初值的设计是dp[0][any]均为1,可以理解为S的空序列等于一个空串,因此为1
  3. 只需要计算j>=i的情况,因为若S比T短,那一定dp一定为0
  4. 思考dp递推式时,定住T的长度,增加S的长度来思考会更容易想明白问题

程序

    public int numDistinct(String s, String t) {
        int lens = s.length();
        int lent = t.length();
        int[][] dp = new int[lent+1][lens+1];

        for(int i = 0; i <= lens; i++)
            dp[0][i] = 1;


        for(int j = 1; j <= lent; j++) {
            for(int i = j; i <= lens; i++) {
                if(s.charAt(i-1) == t.charAt(j-1))
                    dp[j][i] = dp[j-1][i-1]+dp[j][i-1];
                else
                    dp[j][i] = dp[j][i-1];
            }
        }

        return dp[lent][lens];

    }

[leetcode 120] Triangle

题目

一个三角形从顶向下寻找一条和最小的路径

DP解释

  1. 一个最普通的想法:自顶向下, dp[i][j] 为走到第 i 层的第 j 个节点是的和最小路径的和,那么则有 dp[i][j]=min(dp[i1][j1],dp[i1][j]) ,由于每层的信息没有必要都存下来,因此只用存当前层,则空间复杂度为 O(n) 。自顶向下的缺点是:需要特殊处理边界情况,并且还需要求最底层的最小值。
  2. 更简洁的方法:自底向上,考虑到从上向下走的最小值和从下向上走是没有区别的。由于到第i层第j个节点只能走向第i+1层的第j个节点和第j+1个节点,因此从向上走到第i层第j个节点的最小路径的总和表示为 dp[i][j] ,则有 dp[i][j]=min(dp[i+1][j],dp[i+1][j+1]) ,这种方法不需要处理边界情况

程序

  1. 从上到下
    public int minimumTotal(List> triangle) {
        int layer = triangle.size();
        if(layer == 0) return 0;

        int[][] dp = new int[layer][layer];

        int min = triangle.get(0).get(0);
        dp[0][0] = min;
        for(int i = 1; i < layer; i++) {
            int curmin = Integer.MAX_VALUE;
            for(int j = 0; j < i+1; j++) {
                int left = (j-1 >= 0)? dp[i-1][j-1]: Integer.MAX_VALUE;
                int right = (j < i)? dp[i-1][j]: Integer.MAX_VALUE;
                if(i == 2 && j == 2) System.out.println("lr"+left+" "+right);
                dp[i][j] = Math.min(left, right) + triangle.get(i).get(j);
                curmin = Math.min(curmin, dp[i][j]);
            }
            min = curmin;
        }
        return min;
    }
  1. 从下到上
    public int minimumTotal(List> triangle) {
        int layer = triangle.size();
        int[] dp = new int[layer];

        if(layer == 0) return 0;

        //initialiaze
        for(int i = 0; i < layer; i++)
            dp[i] = triangle.get(layer-1).get(i);

        for(int j = layer-2; j >= 0; j--) {
            for(int i = 0; i < j+1; i++)
                dp[i] = Math.min(dp[i], dp[i+1])+triangle.get(j).get(i);
        }

        return dp[0];

    }

[leetcode 357] Count Numbers with Unique Digits

题目

给定一个n,求在[0, 10n )之间的所有数中,数里每一个数字都不一样的数的个数。
比如n=2时,返回91。即为除去100个数中除去{11,22,33…99}的数量。

DP解释

dp[i]代表i位数字里每个数字都不一样的个数。则dp[1]=10. dp[2]=81.
dp[3]=dp[2]x8, dp[4]=dp[3]x7…直到dp[大于等于11]=0。
原因是,对于一个各个位都不同的两位数xy,由于是两位数那么第一位肯定不是0,添加上一个第三位i组成不同的三位数xyi,则i的选择有9种。根据乘法原理,dp[3]=dp[2]x8。其他的同理。
注意问题的拆分。

程序

public class Solution {
    public int countNumbersWithUniqueDigits(int n) {
        int[] dp = new int[11];

        dp[1] = 9;
        for(int i = 2; i <= 10; i++) 
            dp[i] = dp[i-1]*(11-i);

        int sum = 0;
        for(int i = 1; i <= n; i++)
            sum += dp[i];

        return sum+1;

    }
}

[leetcode 279] Perfect Squares

题目

每一个数字都可以写成若干个完全平方数的和,比如12=4+4+4。给定一个数字n,求它最少可以表示为几个完全平方数的和。比如n=12, return 3.

DP解释

dp[i] 表示数字 i 最少可以表示为的完全平方数的和的个数。那么 dp[i]=min(dp[i],dp[isquare]+1) ,其中 square 是每一个小于 i 的完全平方数。

注意

这道题不具有贪心选择性,不能认为挑选完全平方数时越大越好。

程序

    public int numSquares(int n) {
        int[] dp = new int[n+1];
        dp[1] = 1;
        for(int i = 2; i <= n; i++) {
            dp[i] = Integer.MAX_VALUE;

            for(int j = 1; j*j <= i; j++) {
                dp[i] = Math.min(1+dp[i-j*j], dp[i]);
            }
        }
        return dp[n];

    }

你可能感兴趣的:(leetcode)