leetcode day 35 01背包问题 416+1049
0-1背包问题
(1)第一种情况:二维dp[i][j]数组
dp[i][j]表示[0,i]的物品放入容量为j背包的最大价值
不放物品i,dp[i][j]=dp[i-1][j]
放物品i,dp[i][j]=dp[i-1][j-w[i]]+v[i]
递推公式为:
dp[i][j]=dp[i-1][j];//不放
if(w[i]<=j)dp[i][j]=max(dp[i][j],dp[i-1][j-w[i]]+v[i]);
//当前物品体积小于j才可以放
两层for循环,先遍历物品或者先变量背包容量都可以
(2)第二种情况:一维dp[j]数组...相当于上一层数组拷贝下来,也叫滚动数组
dp[j]表示容量为j的背包所能放的最大价值
不放物品i,dp[j]=dp[j]
放物品i,dp[j]=dp[j-w[i]]+v[i]
递推公式为:
dp[j]=max(dp[j],dp[j-w[i]]+v[i])
双重for循环,必须先遍历物品,再遍历背包,同时背包要倒序遍历。
for(int i=0;i<n;i++){
for(int j=t;j>=nums[i];j--){
dp[j]=max(dp[j],dp[j-nums[i]]+nums[i]);
}
}
原因:保证每个物品只添加一次
416 分割等和子集
给你一个 只包含正整数 的 非空 数组 nums
。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
1 <= nums.length <= 200
1 <= nums[i] <= 100
示例 1:
输入:nums = [1,5,11,5] 输出:true 解释:数组可以分割成 [1, 5, 5] 和 [11] 。
示例 2:
输入:nums = [1,2,3,5] 输出:false 解释:数组不能分割成两个元素和相等的子集。
- 背包的体积为sum / 2
- 背包要放入的商品(集合里的元素)重量为 元素的数值,价值也为元素的数值
- 背包如何正好装满,说明找到了总和为 sum / 2 的子集。
- 背包中每一个元素是不可重复放入。
0-1背包问题,相当于物品体积和价值都是nums[i]
对数组求和,sum为奇数一定不满足
求出容量为sum/2时能放入的最大价值,如果刚好放慢,则说明可以分割为等和子集。
根据数组长度和大小,可以确定dp数组的大小,100*200/2=10000
int max(int a,int b){return a>b?a:b;
}
/*0-1背包问题,相当于物品体积和价值都是nums[i]
对数组求和,sum为奇数一定不满足
求出容量为sum/2时能放入的最大价值,如果刚好放慢,则说明可以分割为等和子集
*/
bool canPartition(int* nums, int numsSize) {int sum=0,i,j;int dp[10005]={};//dp[j]表示背包体积为j能放入的最大体积for(i=0;i<numsSize;i++){sum+=nums[i];}if(sum%2!=0)return false;//数组和必为偶数else{for(i=0;i<numsSize;i++){//必须先遍历物品for(j=sum/2;j>=nums[i];j--){//倒序遍历,保证每个元素只添加一次dp[j]=max(dp[j],dp[j-nums[i]]+nums[i]);//放入物品i,dp[j-nums[i]]+nums[i}}}if(dp[sum/2]==sum/2)return true;else return false;
}
1049 最后一块石头的重量
有一堆石头,用整数数组 stones
表示。其中 stones[i]
表示第 i
块石头的重量。
每一回合,从中选出任意两块石头,然后将它们一起粉碎。假设石头的重量分别为 x
和 y
,且 x <= y
。那么粉碎的可能结果如下:
- 如果
x == y
,那么两块石头都会被完全粉碎; - 如果
x != y
,那么重量为x
的石头将会完全粉碎,而重量为y
的石头新重量为y-x
。
最后,最多只会剩下一块 石头。返回此石头 最小的可能重量 。如果没有石头剩下,就返回 0
。
示例 1:
输入:stones = [2,7,4,1,8,1] 输出:1 解释: 组合 2 和 4,得到 2,所以数组转化为 [2,7,1,8,1], 组合 7 和 8,得到 1,所以数组转化为 [2,1,1,1], 组合 2 和 1,得到 1,所以数组转化为 [1,1,1], 组合 1 和 1,得到 0,所以数组转化为 [1],这就是最优值。
示例 2:
输入:stones = [31,26,33,21,40] 输出:5
尽量把石头分为重量相近的两堆,保证相撞后重量最小
所以要尽可能分成重量为 sum / 2 的石头堆,这样剩下的石头堆也是 尽可能接近 sum/2 的重量
转化为背包问题,容量为sum/2时能装入的最大重量
剩下重量为sum-dp[sum/2],sum/2向下取整,所以剩下的重量一定大于dp[sum/2]
最后返回剩下重量与dp[sum/2]的差即可
int max(int a,int b){return a>b?a:b;
}
/*
尽量把石头分为重量相近的两堆,保证相撞后重量最小
所以要尽可能分成重量为 sum / 2 的石头堆,这样剩下的石头堆也是 尽可能接近 sum/2 的重量
转化为背包问题,容量为sum/2时能装入的最大重量
剩下重量为sum-dp[sum/2],sum/2向下取整,所以剩下的重量一定大于dp[sum/2]
最后返回剩下重量与dp[sum/2]的差即可
*/
int lastStoneWeightII(int* stones, int stonesSize) {int dp[15005]={};//dp[j]表示背包容量为j时所能放的最大价值int sum=0;for(int i=0;i<stonesSize;i++){sum+=stones[i];}int t=sum/2;for(int i=0;i<stonesSize;i++){for(int j=t;j>=stones[i];j--){dp[j]=max(dp[j],dp[j-stones[i]]+stones[i]);}}//sum向下取整,所以sum-dp[t]一定大于dp[t]return sum-dp[t]-dp[t];
}
总结:01背包问题,物品数量只有一个时,分为选和不选两种情况讨论
做题步骤
1、确定dp数组含义
2、确定递推公式
3、初始化
4、遍历。注意,二维dp数组遍历无顺序要求,一维dp数组必须先遍历物品,再遍历背包,并且背包要倒序遍历。循环内j>w[i]