day7_动态规划2

一、背包类型问题

1.0-1背包问题

​ (动态规划通用套路)

1.明确 状态 和 选择

​ 状态有两个,背包的容量 和 可选择的物品,选择,对于每件物品选择放入背包 或 不装入背包

2.明确dp数组的定义

dp[i] [w] 的定义; 对于前i个物品,当前背包的容量为W,这种情况下可以装的最大价值

比如 dp[3] [5] = 6 表示若只对前三个物品进行选择,当背包容量为5时,最多可以装下的价值是6

base case 是 dp[0] […] = dp[…] [0] = 0,没有物品或者背包没有空间的时候,能装的最大价值就是0

3.选择

思考状态转移的逻辑

考虑dp[i] [w]:

  1. 如果没有把第i个物品装入,那么最大价值dp[i] [w] = dp[i-1] [w]
  2. 如果把第i个物品装入,那么最大价值 dp[i] [w] = val[i-1] + dp[i-1] [w - wt[i-1]]
    1. 注意val[i-1]就是第i个物品
    2. 拿了第i个物品,那么i-1之前的能装的容量只剩 w - wt[i-1](第i个物品的容量)
int knapsack(int W, int N, int[] wt, int[] val) {
    assert N == wt.length;
    // base case 已初始化,默认创建所有的值都是0
    int[][] dp = new int[N + 1][W + 1];
    for (int i = 1; i <= N; i++) {
        for (int w = 1; w <= W; w++) {
            if (w - wt[i - 1] < 0) {
                // 这种情况下只能选择不装入背包
                dp[i][w] = dp[i - 1][w];
            } else {
                // 装入或者不装入背包,择优
                dp[i][w] = Math.max(
                    dp[i - 1][w - wt[i-1]] + val[i-1], 
                    dp[i - 1][w]
                );
            }
        }
    }
    
    return dp[N][W];
}

2.子集背包问题

分割等和子集

给你一个 只包含正整数非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。

根据1题,先计算出sum/2,之后定义二维数组,dp[i] [sum/2] 表示前i个数总和是否等于sum/2,等于为true,否则为false

boolean canPartition(int[] nums) {
    int sum = 0;
    for (int num : nums) sum += num;
    // 和为奇数时,不可能划分成两个和相等的集合
    if (sum % 2 != 0) return false;
    int n = nums.length;
    sum = sum / 2;
    boolean[][] dp = new boolean[n + 1][sum + 1];
    // base case
    for (int i = 0; i <= n; i++)
        dp[i][0] = true;

    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= sum; j++) {
            if (j - nums[i - 1] < 0) {
                // 背包容量不足,不能装入第 i 个物品
                dp[i][j] = dp[i - 1][j];
            } else {
                // 装入或不装入背包
                //注意 || 的使用非常巧妙,只要有一种情况为true dp[i][j]就为true
                dp[i][j] = dp[i - 1][j] || dp[i - 1][j - nums[i - 1]];
            }
        }
    }
    return dp[n][sum];
}

3.完全背包问题

零钱兑换2

int change(int amount, int[] coins) {
    int n = coins.length;
    int[][] dp = new int[n + 1][amount + 1];
    // base case, dp[0][j]不用初始化,创建时所有的值都为0
    for (int i = 0; i <= n; i++) 
        dp[i][0] = 1;

    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= amount; j++)
            if (j - coins[i-1] >= 0)  // >= 0 表示还有使用硬币的机会
                dp[i][j] = dp[i - 1][j] 
                         + dp[i][j - coins[i-1]];
            else 
                dp[i][j] = dp[i - 1][j];  // < 0 已选择硬币的总面值已经大于j,无法选第i个硬币
    }
    return dp[n][amount];
}
  • 26
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值