LeetCode DP篇(62、63、322、887)

62. 不同路径

一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。

机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。

问总共有多少条不同的路径?

例如,上图是一个7 x 3 的网格。有多少可能的路径?

示例 1:

输入: m = 3, n = 2
输出: 3
解释:
从左上角开始,总共有 3 条路径可以到达右下角。

  1. 向右 -> 向右 -> 向下
  2. 向右 -> 向下 -> 向右
  3. 向下 -> 向右 -> 向右
    示例 2:

输入: m = 7, n = 3
输出: 28

提示:

1 <= m, n <= 100
题目数据保证答案小于等于 2 * 10 ^ 9

solution1 动态规划

class Solution {
    public int uniquePaths(int m, int n) {
        int[] col = new int[n]; //用数组记录之前列的路径
        Arrays.fill(col,1);
        for (int i=1; i<m; i++){
            for (int j=1; j<n; j++){
                col[j] += col[j-1];//第一行永为1,每一个点路径数等于左边+上边(col[j] = col[j]+col[j-1])
            }
        }
        return col[n-1];
    }
}
class Solution {
    public int uniquePaths(int m, int n) {
        int[][] chart = new int[m][n];
        for (int i=0; i<m; i++) chart[i][0] = 1;
        for (int i=0; i<n; i++) chart[0][i] = 1;
        for (int i=1; i<m; i++){
            for (int j=1; j<n; j++){
                chart[i][j] = chart[i-1][j] + chart[i][j-1];
            }
        }
        return chart[m-1][n-1];
    }
}

63. 不同路径 II

一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。

机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。

现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?

网格中的障碍物和空位置分别用 1 和 0 来表示。

说明:m 和 n 的值均不超过 100。

示例 1:

输入:
[
[0,0,0],
[0,1,0],
[0,0,0]
]
输出: 2
解释:
3x3 网格的正中间有一个障碍物。
从左上角到右下角一共有 2 条不同的路径:

  1. 向右 -> 向右 -> 向下 -> 向下
  2. 向下 -> 向下 -> 向右 -> 向右

solution1 DP

class Solution {
    public int uniquePathsWithObstacles(int[][] obstacleGrid) {
        int height = obstacleGrid.length;
        int[] col = new int[height];
        col[0] = 1;
        for (int i=0; i<obstacleGrid[0].length; i++){
            for (int j=0; j<obstacleGrid.length; j++){
                if (obstacleGrid[j][i] == 1) col[j] = 0;
                else if (j>0) col[j] += col[j-1];
            }
        }
        return col[height-1];
    }
}
class Solution {
    public int uniquePathsWithObstacles(int[][] obstacleGrid) {
        int width = obstacleGrid[0].length;
        int[] dp = new int[width];
        dp[0] = 1;
        for (int[] row : obstacleGrid){
            for (int i=0; i<width; i++){
                if (row[i] == 1) dp[i] = 0;
                else if (i>0) dp[i] += dp[i-1];
            }
        }
        return dp[width-1];
    }

322. 零钱兑换

给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。
示例 1:

输入: coins = [1, 2, 5], amount = 11
输出: 3
解释: 11 = 5 + 5 + 1
示例 2:

输入: coins = [2], amount = 3
输出: -1

说明:
你可以认为每种硬币的数量是无限的。

solution1 暴力递归 (自顶向下)

class Solution {
    public int coinChange(int[] coins, int amount) {
        return helper(coins,amount);
    }
    public int helper(int[] coins, int n){
        if(n == 0) return 0;
        if(n < 0) return -1;
        int res = n + 1;
        for(int coin:coins){
            int subres = helper(coins,n - coin); //状态转移方程
            if(subres == -1) continue;
            res = Math.min(res,subres+1);//最优子结构
        }
        return res >= n+1 ? -1:res;
    }
}

solution2 带备忘录递归

class Solution {
    public int coinChange(int[] coins, int amount) {
        int[] count = new int[amount];
        return helper(coins,amount,count);
    }
    public int helper(int[] coins, int n,int[] count){
        if(n == 0) return 0;
        if(n < 0) return -1;
        if(count[n-1] != 0) return count[n-1];
        int res = n + 1;
        for(int coin:coins){
            int subres = helper(coins,n - coin,count); //状态转移方程
            if(subres == -1) continue;
            res = Math.min(res,subres+1);//最优子结构
        }
        return count[n-1] = res >= n+1 ? -1:res;
    }
}

solution3 动态规划 自底向上

class Solution {
    public int coinChange(int[] coins, int amount) {
        int max = amount + 1;
        int[] dp = new int[amount+1];
        Arrays.fill(dp,max);
        dp[0] = 0;
        for (int i = 1; i <= amount; i++){
            for (int j = 0; j < coins.length; j++){
                if(coins[j] <= i){
                    dp[i] = Math.min(dp[i],dp[i-coins[j]]+1); //状态转移方程
                }
            }
        } 
        return dp[amount] > amount ? -1:dp[amount];
    }
  
}

思路

// 参考博客:https://leetcode-cn.com/problems/coin-change/solution/dong-tai-gui-hua-tao-lu-xiang-jie-by-wei-lai-bu-ke/
# 初始化 base case
dp[0][0][...] = base
# 进行状态转移
for 状态1 in 状态1的所有取值:
    for 状态2 in 状态2的所有取值:
        for ...
            dp[状态1][状态2][...] = 求最值(选择1,选择2...)

887. 鸡蛋掉落

你将获得 K 个鸡蛋,并可以使用一栋从 1 到 N 共有 N 层楼的建筑。

每个蛋的功能都是一样的,如果一个蛋碎了,你就不能再把它掉下去。

你知道存在楼层 F ,满足 0 <= F <= N 任何从高于 F 的楼层落下的鸡蛋都会碎,从 F 楼层或比它低的楼层落下的鸡蛋都不会破。

每次移动,你可以取一个鸡蛋(如果你有完整的鸡蛋)并把它从任一楼层 X 扔下(满足 1 <= X <= N)。

你的目标是确切地知道 F 的值是多少。

无论 F 的初始值如何,你确定 F 的值的最小移动次数是多少?

示例 1:

输入:K = 1, N = 2
输出:2
解释:
鸡蛋从 1 楼掉落。如果它碎了,我们肯定知道 F = 0 。
否则,鸡蛋从 2 楼掉落。如果它碎了,我们肯定知道 F = 1 。
如果它没碎,那么我们肯定知道 F = 2 。
因此,在最坏的情况下我们需要移动 2 次以确定 F 是多少。
示例 2:

输入:K = 2, N = 6
输出:3
示例 3:

输入:K = 3, N = 14
输出:4

提示:

1 <= K <= 100
1 <= N <= 10000

solution1 备忘录+递归

class Solution {
    public int superEggDrop(int K, int N) {
        int[][] menu = new int[K+1][N+1];
        return helper(K,N,menu);
    }
    public int helper(int k, int n, int[][] menu){
        //base case
        if (k == 1) return n;
        if (n == 0) return 0;
        if (menu[k][n] != 0) return menu[k][n];
        int res = Integer.MAX_VALUE;
        for(int i = 1; i < n+1; i++){
            //不碎,往上投还剩下n-i层 k不变
            //碎了,往下投i-1,k-1
            res = Math.min(res,Math.max(helper(k,n-i,menu),helper(k-1,i-1,menu))+1);
        }
        menu[k][n] = res;
        return res;
    }
}
//思路1:递归+备忘录
//1.dp数组,两个维度:还剩下几层 还剩多少鸡蛋(鸡蛋个数可以影响剩下投法,剩下层数影响鸡蛋怎么投)
//2.base case
//3.计算每一层投下后 碎和不碎两种情况下还要投的次数,取最小值

solution 2 二分查找优化

class Solution {
    public int superEggDrop(int K, int N) {
        return helper(K,N);
    }
    Map<Integer, Integer> memo = new HashMap();
    public int helper(int k, int n){
        //base case
        if (k == 1) return n;
        if (n == 0) return 0;
        if (memo.containsKey(n*100+k)) return memo.get(100*n+k);
        int res = Integer.MAX_VALUE;
        //二分查找
        int l = 1, r = n;
        
        while (l <= r){
            int mid = (l+r) >> 1;
            int noBroken = helper(k,n-mid);
            int broken = helper(k-1,mid-1);
            if (broken > noBroken){
                r = r - 1;
                res = Math.min(res,broken + 1);
            }else{
                l = l + 1;
                res = Math.min(res,noBroken+1);
            }
        }
        memo.put(n*100+k,res);
        return memo.get(n*100+k);
    }
}
posted @ 2020-07-20 21:07  gg12138  阅读(235)  评论(0编辑  收藏  举报