代码随想录算法训练营第四十二天 | 01背包问题

这篇博客详细介绍了背包理论基础,包括二维数组表示的01背包问题和使用滚动数组的一维01背包问题。文章通过动规五部曲阐述了如何解决背包问题,并以416. 分割等和子集为例,展示了如何将01背包问题应用到实际题目中。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

背包理论基础

01 背包(二维数组)

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

背包最大重量为4,其中物品为:

重量价值
物品0115
物品1320
物品2430

需要求背包能背的物品最大值为多少?

动规五部曲:

  1. 确定dp数组以及下标的函数

定义一个二维数组dp[i][j]表示下标从[0,i]的物品里任取,放进容量为j的背包当中,背包的价值总和为dp[i][j]

在这里插入图片描述

  1. 确定递推公式

①不放物品i:可以由dp[i-1][j]退出,背包里面没有放物品i,所以仍然为前一个的价值

②放物品i:可以由dp[i-1][j-weight[i]]推出,价值为dp[i-1][j-weight[i]]+value[i]

所以递归公式可以总结为dp[i][j]=max(dp[i-1][j],(dp[i-1][j-weight[i]]+value[i]))

  1. dp数组初始化

①当j=0的时候dp[j][0]=0

在这里插入图片描述

②由递归公式可知dp[i]需要依赖dp[i-1]所以我们需要确定dp[0][j]

当j<weight[0]的时候,dp[0][j]=0,背包容量小于第一个物品,所以价值为0

当j≥weigh[0]的时候,dp[0][j]=value[0]

在这里插入图片描述

  1. 确定遍历顺序

先遍历物品i,在遍历背包重量j

  1. 距离推导dp数组

在这里插入图片描述

最终结果为dp[2][4]

  public int testWeightBagProblem(int[] weight, int[] value, int bagSize){
        int[][] dp=new int[weight.length][bagSize+1];
        //初始化数组,将背包数量大于物品0的赋值物品0的value,其他的默认为0
        for(int i=weight[0];i<=bagSize;i++){
            dp[0][i]=value[0];
        }

        //开始遍历
        for(int i=1;i<=weight.length;i++){
            for(int j=1;j<=bagSize;j++){
                //当前背包的容量都没有当前物品i大的时候,是不放物品i的,
                // 那么前i-1个物品能放下的最大价值就是当前情况的最大价值
                if(j<weight[i]){
                    dp[i][j]=dp[i-1][j];
                }else{
                    dp[i][j]=Math.max(dp[i-1][j],dp[i-1][j-weight[i]]+value[i]);
                }
            }
        }
        return dp[weight.length-1][bagSize];
    }

01 背包(一维—滚动数组)

动规五部曲:

  1. 确定dp数组及其下标含义

dp[j]表示容量为j的背包,所背的物品价值可以最大为dp[j]

  1. 一维dp数组的递推公式

dp[j]可以由dp[j-weight[i]]推导出来,dp[j]有两个选择,一个是取自己dp[j]相当于二维dp数组中的dp[i-1][j],即不放物品i,一个是取dp[j-weight[i]]+value[i],即放物品i

所以递推公式为 dp[j]=max(dp[j],dp[j-weight[i]]+value[i])

  1. 一维数组初始化

dp[0]=0;

  1. 一维dp数组遍历顺序

一维遍历dp的时候,背包需要从大到小(倒序遍历是为了保证物品i只被放入一次)

  1. 举例推导dp数组

用物品0遍历:0 15 15 15 15

用物品1遍历:0 15 15 20 35

用物品2遍历:0 15 15 20 35

  public int testWeightBagProblem(int[] weight, int[] value, int bagSize){
        int[] dp=new int[bagSize+1];
        for(int i=0;i<=weight.length;i++){
            for(int j=bagSize;j>=weight[i];j--){
                dp[i]=Math.max(dp[i],dp[j-weight[i]]+value[i]);
            }
        }
        return dp[bagSize];
    }

416. 分割等和子集

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

输入:nums = [1,5,11,5]
输出:true
解释:数组可以分割成 [1, 5, 5][11]

这道题元素只是使用一次,所以可以考虑01背包问题

这道题就是需要求解集合中是否能够出现sum/2的子集

将01背包问题套到该题中:

  1. 背包的体积为sum/2
  2. 背包要放入的商品重量为元素的数值,价值也为元素的数值
  3. 背包如果正好装满,说明找到了sum/2的子集
  4. 背包中每一个元素是不可重负放入的

动规五部曲:

  1. 确定dp数组及下标含义

如果背包容量为target(sum/2),dp[target]==target的时候背包就装满了

所以dp[j]表示背包容量为j,背包的最大重量为dp[j]

  1. 确定递推公式

01 背包问题的递推公式为dp[j]=max(dp[j],dp[j-weight[i]]+value[i])

weight[i]就是nums[i],其价值也是nums[i]

所以本题的递推公式为dp[j]=max(dp[j],dp[j-nums[i]]+nums[i])

  1. dp数组初始化

dp[0]=0,题目中质保函正整数,所以令非零下标元素初始化为0就可以了

  1. 确定遍历顺序

背包需要从大到小(数组从后向前遍历)

  1. dp数组推导

在这里插入图片描述

class Solution {
    public boolean canPartition(int[] nums) {
        if(nums==null||nums.length==0){
            return false;
        }
        int sum=0;
        for(int i:nums){
            sum+=i;
        }
        if(sum%2!=0) return false;
        int target=sum/2;
        int[] dp=new int[target+1];
        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]);
            }
        }
        if(dp[target]==target){
            return true;
        }
        return false;

    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值