背包理论基础
01 背包(二维数组)
有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。
背包最大重量为4,其中物品为:
重量 | 价值 | |
---|---|---|
物品0 | 1 | 15 |
物品1 | 3 | 20 |
物品2 | 4 | 30 |
需要求背包能背的物品最大值为多少?
动规五部曲:
- 确定dp数组以及下标的函数
定义一个二维数组dp[i][j]表示下标从[0,i]的物品里任取,放进容量为j的背包当中,背包的价值总和为dp[i][j]
- 确定递推公式
①不放物品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]))
- 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]
- 确定遍历顺序
先遍历物品i,在遍历背包重量j
- 距离推导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 背包(一维—滚动数组)
动规五部曲:
- 确定dp数组及其下标含义
dp[j]表示容量为j的背包,所背的物品价值可以最大为dp[j]
- 一维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])
- 一维数组初始化
dp[0]=0;
- 一维dp数组遍历顺序
一维遍历dp的时候,背包需要从大到小(倒序遍历是为了保证物品i只被放入一次)
- 举例推导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背包问题套到该题中:
- 背包的体积为sum/2
- 背包要放入的商品重量为元素的数值,价值也为元素的数值
- 背包如果正好装满,说明找到了sum/2的子集
- 背包中每一个元素是不可重负放入的
动规五部曲:
- 确定dp数组及下标含义
如果背包容量为target(sum/2),dp[target]==target的时候背包就装满了
所以dp[j]表示背包容量为j,背包的最大重量为dp[j]
- 确定递推公式
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])
- dp数组初始化
dp[0]=0,题目中质保函正整数,所以令非零下标元素初始化为0就可以了
- 确定遍历顺序
背包需要从大到小(数组从后向前遍历)
- 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;
}
}