leetcode之动态规划(一)

目录

1.climbing-stairs

2.triangle

3.candy

4.decode-ways

5.gray-code

6.unique-paths

7.unique-paths-ii

8.minimum-path-sum

9.subsets

10.subsets-ii


1.climbing-stairs

题目:你在爬楼梯,需要n步才能爬到楼梯顶部。每次你只能向上爬1步或者2步。有多少种方法可以爬到楼梯顶部?

分析:动态规划,dp[n] = dp[n-1] + dp[n-2]

   public int climbStairs(int n) {
        int x = 1,y = 1;
        for(int i = 2;i <= n;i++){
            int temp = x + y;
            x = y;
            y = temp;
        }
        return y;
    }

2.triangle

题目:给出一个三角形,计算从三角形顶部到底部的最小路径和,每一步都可以移动到下面一行相邻的数字。例如,给出的三角形如下:[↵ [2],↵ [3,4],↵ [6,5,7],↵ [4,1,8,3]↵],最小的从顶部到底部的路径和是2 + 3 + 5 + 1 = 11。注意:如果你能只用O(N)的额外的空间来完成这项工作的话,就可以得到附加分,其中N是三角形中的行总数。

分析:自下而上计算,由于只能移动到相邻位置,当前位置的最小值就是下层两个相邻数的最小值加上当前值。状态转移方程 :dp[i][j]= min(dp[i+1][j],dp[i+1][j+1]) + dp[i,j].

//    [                   
//         [2],                 [2],              
//        [3,4],              [3, 4],            [2],
//       [6,5,7],      ==>   [7, 6, 10]     ==>  [9, 10]   ==>     [11]
//      [4,1,8,3]
//    ]    
   public int minimumTotal(ArrayList<ArrayList<Integer>> triangle) {
        if(triangle == null)
            return 0;
        int size = triangle.size();
        int [] result = new int[size];
        for(int i=0;i < size;i++)
            result[i] = triangle.get(size-1).get(i);
        for(int row = size-2;row >= 0;row--){
            for(int j = 0;j <= row;j++){
                result[j] = Math.min(result[j],result[j+1]) + triangle.get(row).get(j);
            }
        }
        return result[0];
    }

3.candy

题目:有N个小朋友站在一排,每个小朋友都有一个评分,你现在要按以下的规则给孩子们分糖果:每个小朋友至少要分得一颗糖果,分数高的小朋友要他比旁边得分低的小朋友分得的糖果多,你最少要分发多少颗糖果?

分析:先给每个小朋友都分一颗糖,从左到右扫描保证一个方向上分数高的糖更多,然后从右到左扫描保证另一个方向上分数高的糖更多,注意第二次分配时分数高的糖更多不再进行分配。

    public int candy(int[] ratings) {
        int len = ratings.length;
        if(len < 2)
            return len;
        int[] candy = new int[len];
        for(int i = 1;i < len;i++){//从左到右
            if(ratings[i-1] < ratings[i])
                candy[i] = candy[i-1] + 1;
        }
        int sum = len + candy[len - 1];
        for(int i = len - 2;i >= 0;i--){//从右到左
            if(ratings[i] > ratings[i+1] && candy[i] <= candy[i+1])
                candy[i] = candy[i+1] + 1;
            sum += candy[i];
        }
        return sum;
    }

4.decode-ways

题目:一条仅包含字母‘A’-‘Z’的消息用下列的方式加密成数字'A' -> 1↵'B' -> 2↵...↵'Z' -> 26,现在给出加密成数字的密文,请判断有多少种解密的方法。例如:给出的密文为“12”,可以解密为"AB"(1 2) 或者"L"(12).所以密文"12"的解密方法是2种.

分析:动态规划。dp[i] 表示密文中前i个字符解密方法的种数,状态转移方程为:dp[i] = dp[i-1] + dp[i-2]。dp[i-1]表示加入单个字符(当前字符为‘0’时不能只加入单个字符),dp[i-2]表示当前字符与前一个字符凑(前面字符为1或2的时候才能凑)。易错:字符串不以0开头dp[1]才为1。

    public int numDecodings(String s) {
        if(s.length() == 0 || s.charAt(0) == '0')
            return 0;
        int[] dp = new int[s.length() + 1];
        dp[0] = 1;dp[1] = 1;
        for(int i = 2;i <= s.length();i++){
            if(s.charAt(i-1) == '0')
                dp[i] = 0;
            else
                dp[i] = dp[i-1];//当前字符不为0才能单独解密
            if(s.charAt(i-2) == '1' || s.charAt(i-2) == '2' && s.charAt(i-1) <'7')
                dp[i] += dp[i-2];
        }
        return dp[s.length()];
    }

5.gray-code

题目:格雷码是一种二进制编码系统,如果任意两个相邻的代码只有一位二进制数不同,则称这种编码为格雷码(Gray Code)。给定一个非负整数n,表示代码的位数,打印格雷码的序列。格雷码序列必须以0开头。例如:给定n=2,返回[0,1,3,2]

分析:当n = 0,格雷码为[0];当n = 1,格雷码为[0,1];当n = 2,格雷码为[00,01,11,10];当n = 3,格雷码为[000,001,011,010,110,111,101,100];后面每一个list的元素等于前面list每个元素前面加‘0’,和逆序遍历list每个元素前面加‘1’。

   public ArrayList<Integer> grayCode(int n) {
        ArrayList<Integer> result = new ArrayList<>();
        if(n == 0){
            result.add(0);
            return result;
        }
        ArrayList<String> pre = new ArrayList<>();
        ArrayList<String> cur;
        pre.add("0");pre.add("1");
        for(int i = 2;i <= n;i++){
            cur = new ArrayList<>();
            for(String s : pre)
                cur.add("0" + s);
            for(int j = pre.size() - 1;j >= 0;j--)
                cur.add("1" + pre.get(j));
            pre = cur;
        }
        for(String s : pre)
            result.add(Integer.parseInt(s,2));
        return result;
    }

6.unique-paths

题目:一个机器人在m×n大小的地图的左上角(起点,下图中的标记“start"的位置)。机器人每次向下或向右移动。机器人要到达地图的右下角。(终点,下图中的标记“Finish"的位置)。可以有多少种不同的路径从起点走到终点?

上图是3×7大小的地图,有多少不同的路径?备注:m和n小于等于100

分析:动态规划。dp[m][n] = dp[m-1][n] + dp[m][n-1]

   public int uniquePaths(int m, int n) {
        int[][]dp = new int[m][n];
        for(int i = 0;i < n;i++)
            dp[0][i] = 1;
        for(int i = 0;i < m;i++)
            dp[i][0] = 1;
        for(int row = 1;row < m;row++){
            for(int col = 1;col < n;col++){
               dp[row][col] = dp[row-1][col] + dp[row][col-1];
            }
        }
        return dp[m-1][n-1];
    }

7.unique-paths-ii

题目:继续思考题目"Unique Paths":如果在图中加入了一些障碍,有多少不同的路径?分别用0和1代表空区域和障碍。例如,下图表示有一个障碍在3*3的图中央。[↵ [0,0,0],↵ [0,1,0],↵ [0,0,0]↵]有2条不同的路径。备注:m和n不超过100.

分析:动态规划,在上题基础上考虑障碍的情况,在初始化和赋值多考虑一种情况即可。

   public int uniquePathsWithObstacles(int[][] obstacleGrid) {
       int row = obstacleGrid.length;
       if(row == 0)
           return 0;
       int col = obstacleGrid[0].length;
       int[][] dp = new int[row][col];
       int i = 0;
       while(i < col && obstacleGrid[0][i] == 0)
           dp[0][i++] = 1;
       while(i < col)
           dp[0][i++] = 0;
       i = 0;
        while(i < row && obstacleGrid[i][0] == 0)
            dp[i++][0] = 1;
        while(i < row)
            dp[i++][0] = 0;
        for(i = 1;i < row;i++){
            for(int j = 1;j < col;j++){
                if(obstacleGrid[i][j] == 1)
                    dp[i][j] = 0;
                else
                    dp[i][j] = dp[i-1][j] + dp[i][j-1];
            }
        }
        return dp[row-1][col-1];
    }

8.minimum-path-sum

题目:给定一个由非负整数填充的m x n的二维数组,现在要从二维数组的左上角走到右下角,请找出路径上的所有数字之和最小的路径。注意,你每次只能向下或向右移动。
分析:见剑指offer面试题47  https://blog.csdn.net/Nibaby9/article/details/10401824

9.subsets

题目:现在有一个没有重复元素的整数集合S,求S的所有子集。注意:你给出的子集中的元素必须按非递增的顺序排列;给出的解集中不能出现重复的元素。例如:如果S=[1,2,3], 给出的解集应为:[↵ [3],↵ [1],↵ [2],↵ [1,2,3],↵ [1,3],↵ [2,3],↵ [1,2],↵ []↵]

分析:将问题转化为集合S含k个元素的子集,注意空集也是子集的其中一个!!求集合中含固定元素的子集用回溯法即可。

    public ArrayList<ArrayList<Integer>> subsets(int[] S) {
        ArrayList<ArrayList<Integer>> result = new ArrayList<>();
        if(S.length == 0)
            return result;
        ArrayList<Integer> list = new ArrayList<>();
        Arrays.sort(S);
        for(int k = 0;k <= S.length;k++)
            util(S,result,0,k,list);//添加k个元素(注意空集也是子集),从下标0开始
        return result;
    }

    private void util(int[] s, ArrayList<ArrayList<Integer>> result, int index, int k, ArrayList<Integer> list) {
        if(k == 0){
            result.add(new ArrayList<Integer>(list));
            return;
        }
        for(int i = index;i < s.length;i++){
            list.add(s[i]);
            util(s,result,i + 1,k - 1,list);
            list.remove(list.size() - 1);
        }
    }

10.subsets-ii

题目:给出一个可能包含重复元素的整数集合S,返回该整数集合的所有子集。注意:给出的子集中的元素要按非递增的顺序排列;给出的解集中不能包含重复的子集。例如:如果S =[1,2,2], 给出的解集应该是:[↵ [2],↵ [1],↵ [1,2,2],↵ [2,2],↵ [1,2],↵ []↵]

分析:只要在上题的基础上进行重复元素的处理即可。

    public ArrayList<ArrayList<Integer>> subsetsWithDup(int[] num) {
        ArrayList<ArrayList<Integer>> result = new ArrayList<>();
        if(num == null || num.length == 0)
            return result;
        ArrayList<Integer> list = new ArrayList<>();
        Arrays.sort(num);
        util(result,list,num,0);
        return result;
    }

    private void util(ArrayList<ArrayList<Integer>> result, ArrayList<Integer> list, int[] num, int index) {
        result.add(new ArrayList<>(list));
        for(int i = index;i < num.length;i++){
            if(i != index && num[i] == num[i-1])
                continue;
            list.add(num[i]);
            util(result,list,num,i+1);
            list.remove(list.size()-1);
        }
    }

猜你喜欢

转载自blog.csdn.net/Nibaby9/article/details/104516367