LeetCode DP篇(62、63、322、887)
62. 不同路径
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。
问总共有多少条不同的路径?
例如,上图是一个7 x 3 的网格。有多少可能的路径?
示例 1:
输入: m = 3, n = 2
输出: 3
解释:
从左上角开始,总共有 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 条不同的路径:
- 向右 -> 向右 -> 向下 -> 向下
- 向下 -> 向下 -> 向右 -> 向右
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);
}
}