代码随想录算法训练营第三十天|0/1背包问题
二维
有n件物品和一个最多能背重量为w的背包。第i件物品的重量是w[i],得到的价值是v[i]。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。
1.确定dp数组以及下标的含义:
dp[i][j]表示从下标为[0~i]的物品里任意取,放进容量为j的背包,能得到最大的价值。
2.确定递推公式:
对于dp[i][j]有两种情况:放物品i;不放物品i。
如果不放物品i,dp[i][j]=dp[i-1][j];
如果放物品i,那么背包要先留出物品i的容量,背包容量变为j-w[i],所以dp[i][j]=dp[i-1][j-w[i]]+v[i]。
所以递推公式:。
3.初始化:
首先如果背包容量j为0的话,背包价值一定为0,也就是dp[i][0]=0。
由递推公式可以看出i是由i-1推导出来的,那么i为0的时候就一定要初始化。所以当j<w[0]时,dp[0][j]=0;当j>=w[0]时,dp[0][j]=v[0]。
for (int i = 1; i < w.length; i++) { // 当然这一步,如果把dp数组预先初始化为0了,这一步就可以省略dp[i][0] = 0;
}
for (int j = w[0]; j <= bagweight; j++) {dp[0][j] = v[0];
}
4.确定遍历顺序:
有两个维度的遍历:物品与背包重量。
先遍历物品,然后再遍历背包重量:
for (int i = 1; i < n; i++) {for (int j = 0; j <= bagweight; j++) {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]);}}}
5.举例推导dp数组:
一维
1.确定dp数组以及下标的含义:
dp[j]表示容量为j的背包所背的物品价值最大可以为dp[j] 。
2.确定递推公式:
二维dp数组的递推公式为:。可以发现如果把dp[i-1]那一层拷贝到dp[i]上,表达式完全可以是:
所以可以把i这个维度去掉,也就是:。
3.初始化:默认都初始化为0即可。
4.确定遍历顺序:
先遍历物品,再遍历背包容量:
for (int i = 0; i < n ; i++) {for (int j = bagweight ; j >= w[i]; j--) {dp[j] = Math.max(dp[j], dp[j - w[i]] + v[i]);}}
这里可以看出,一维dp遍历背包是从大到小遍历的,这和二维dp遍历不同,为什么?
如果背包正序遍历,物品i可能被放入多次。
比如:物品0的重量weight[0] = 1,价值value[0] = 15,如果正序遍历:
dp[1] = dp[1 - w[0]] + v[0] = 15
dp[2] = dp[2 - w[0]] + v[0] = 30
那么物品0就被放入了两次,所以不能正序遍历。
而倒序遍历:
dp[2] = dp[2 - w[0]] + v[0] = 15 (dp数组已经都初始化为0)
dp[1] = dp[1 - w[0]] + v[0] = 15
这样就能保证每种物品只取一次。
而二维dp遍历不需要倒序是因为dp[i][j]都是通过上一层dp[i-1][j]计算而来的,本层的dp[i][j]是不会被覆盖的。
5.举例推导dp数组:还是之前的例子
LeetCode 416 分割等和子集
题目链接: 416. 分割等和子集 - 力扣(LeetCode)
给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
示例 1:
输入:nums = [1,5,11,5]
输出:true
解释:数组可以分割成 [1, 5, 5] 和 [11] 。
示例 2:输入:nums = [1,2,3,5]
输出:false
解释:数组不能分割成两个元素和相等的子集。
首先求出nums数组的元素和ans,如果ans为奇数,直接返回false即可。
nums数组相当于0/1背包问题中的容量数组和价值数组,判断能否将nums数组分割成链各个元素和相等的自己,那就相当于判断能否将容量为ans/2的背包装满。
因为容量和价值是相等的,所以最后容量为ans/2的背包的最大价值为ans/2,说明能装满,返回true,否则返回false。本质和0/1背包问题是一样的。
代码如下:
class Solution {public boolean canPartition(int[] nums) {int ans=0;int n=nums.length;for(int i=0;i<n;i++)ans+=nums[i];if(ans%2==1)return false;int[] dp=new int[ans/2+1];for(int i=0;i<n;i++){for(int j=ans/2;j>=nums[i];j--){dp[j]=Math.max(dp[j],dp[j-nums[i]]+nums[i]);}}if(dp[ans/2]==ans/2)return true;else return false;}
}