【代码随想录训练营】Day42-动态规划

代码随想录训练营 Day42

今日任务

01背包问题二维
01背包问题一维
416.分割等和子集
语言:Java

背包问题辨析

01背包:物品只有一个
完全背包:物品有无数个
多重背包:不同的物品数量不同
分组背包:按组打包,每组最多选一个

01背包问题-二维数组

背包问题:有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。

  1. dp数组及下标含义:从0-i个物品中挑选物品,放到最大容量为j的背包中得到的最大价值为dp[i][j]
  2. 递归公式:
    if(j < weight[i]) dp[i][j] = dp[i-1][j]
    dp[i][j] = max(dp[i-1][j], dp[i-1][j-weight[i]] + value[i])
    dp[i-1][j]:代表不将第i个物品放入容量为j的背包中的价值
    dp[i-1][j-weight[i]]:代表从0~i-1个物品中挑选物品,放到最大容量为j-weight[i]的背包中得到的最大价值
    dp[i-1][j-weight[i]]+value[i]:代表将第i个物品放入容量为j的背包后的价值
  3. 初始化:
    dp[i][0]=0,不管是遍历第几个物品,如果背包最大容量就是0,那么得到的最大价值也一定是0;
    dp[0][j]:因为目前只有物品0,所以只需要考虑物品0的重量和价值
    if(weight[0] > bagweight[j]) dp[0][j] = 0;
    if(weight[0] <= bagweight[j] dp[0][j] = value[0];
  4. 确定遍历顺序:先遍历物品或先遍历背包都是可以的,在初始化的时候要紧盯递归公式,明确递归公式中需要的值来自于哪里;
    ① 先遍历物品i,当我们遍历到dp[i][j]时,dp数组中第i行以上的所有元素都已经被赋值了,所以我们可以正确得到dp[i][j]的值
    ② 先遍历背包j,当我们遍历到dp[i][j]时,dp数组中第j列左侧所有的列都已经被赋值,虽然我们需要用到第i-1行第j列的元素,但因为我们在遍历物品的时候是从小到大遍历的,所以dp[i-1][j]也已经被赋值了,我们可以正确得到dp[i][j]的值
  5. 打印dp数组

01背包问题-一维滚动数组

二维数组的改进,我们注意到二维数组的递归公式中,我们需要使用到的都是上一行的数值,所以可以考虑用一维数组

  1. dp数组及下标含义:dp[j]代表容量为j的背包,可以获得的物品最大价值为dp[j]
  2. 递归公式:
    if(j < weight[i]) dp[j] = dp[j]
    dp[j] = max(dp[j], dp[j - weight[i]] + value[i]
  3. 初始化:dp[0]=0,根据递归公式,我们每次取的是最大值,所以如果题目给的值都是正数,那么我们直接将其他下标处的值也初始化为0即可
  4. 遍历顺序:
    ① 为什么要倒序遍历?
    保证物品i只被放入一次
    以物品0为例,weight[0]=1,value[0]=15,正序遍历:
    dp[1] = max(dp[1], dp[1 - weight[0]] + value[0]) //15
    dp[2] = max(dp[2], dp[2 - weight[0]] + value[0]) //30
    我们可以看到,如果目前只是在放物品0的状态,那么在背包容量大于等于1时,所有的背包最大价值都应该为15(因为是01背包,一种物品只有一个),而如果按照背包容量从小到大遍历,就会有重复计入相同物品的问题。
    ② 为什么倒序遍历可以保证只计入物品一次呢?
    依旧以物品0为例,weight[0]=1,value[0]=15,倒序遍历:
    dp[2] = max(dp[2], dp[2 - weight[0]] + value[0]) //15
    dp[1] = max(dp[1], dp[1 - weight[0]] + value[0]) //15
    ③ 为什么二维数组不用倒序遍历呢?
    可以参考下图中的过程理解:
    当我们使用二维数组计算3的值时,需要参考上一行1和2的值,但又因为我们遍历完第i-1行后就不会再修改该行中元素的值,所以不用担心下一行遍历顺序的问题;
    当我们使用一维数组计算3的值时,需要参考当前数组1和2位置的旧值,如果使用前序遍历而非倒序遍历,那么在遍历到3的位置时,1位置的值已经不再是旧值,而是新值,会导致3的结果出错。
    在这里插入图片描述
    ④ 为什么必须先遍历物品再遍历背包,可以反过来吗?
    必须先遍历物品再遍历背包,反过来相当于背包只放入了一个物品
    先遍历背包再遍历物品的循环逻辑如下:
    for(int j = bagweight.length; j >= weight[i]; j--){
    	for(int i = 0; i < weight.length; i++){
    		//...
    	}
    }
    
    我们依旧是要搞清楚dp数组的含义以及i和j的含义,dp现在只是一个一维数组,如果先遍历背包,相当于先固定j,再通过不断改变i来修改dp[i][j]的值。因为元素初始化为0以及后序遍历的缘故,相当于每次只向背包中放入了一个物品,这和dp数组的含义产生了冲突。
    在这里插入图片描述
  5. 打印dp数组

416. 分割等和子集

链接:https://leetcode.cn/problems/partition-equal-subset-sum/
题目转换:
① 背包:让背包容量代表子集的和
② 物品:让数组中的元素代表物品

class Solution {
    public boolean canPartition(int[] nums) {
        //1.明确dp数组及下标含义: 容量为j的背包得到的子集和最大为dp[j]
        //2.递归公式: dp[j]=max(dp[j],dp[j-weight[i]]+value[i])
        //3.初始化: 0
        //4.遍历顺序: 外层遍历物品,内层遍历背包,背包从后向前遍历
        //5.打印dp数组
        int sum = 0;
        for(int i = 0; i < nums.length; i++){
            sum += nums[i];
        }
        if(sum %2 == 1) return false; //和为奇数,肯定不会构成两个相同的背包
        int target = sum / 2;
        //nums数组中的每个元素代表重量为nums[i]价值为nums[i]的物品
        //weight[i]=value[i]=nums[i]
        //背包的最大容量就是每个子集能达到的最大的和,根据题意最大为200×100÷2
        int[] dp = new int[10001];
        for(int i = 0; i < nums.length; i++){
            for(int j = target; j >= nums[i]; j--){
                dp[j] = Math.max(dp[j], dp[j - nums[i]] + nums[i]);
            }
        }
        return dp[target] == target;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值