01背包理论基础
思路
dp[i][j]: 表示从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少。
递推公式:
- 不放物品i dp[i-1][j]
- 放物品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数组初始化
遍历顺序 本题先遍历物品还是先遍历背包是一样的效果
function BagProblem(weight, value, size){
let len = weight.length
let dp = Array(len).fill().map(()=>Array(size+1).fill(0))
//初始化
for(let j = weight[0]; j<=size; j++){
dp[0][j] = value[0]
}
for(let i =1; i<len; i++){ //遍历物品
for(let j=0; j<=size; 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])
}
}
return dp[len-1][size]
}
01背包理论基础(滚动数组)
思路
相对于二维数组实现背包问题,使用一维数组实现其实就是将二维数组压缩成一维的,每一次遍历使用的就是上一行的数值即dp[j]
dp[j]: 表示背包重量为j时所背的物品价值最大可以是dp[j]
递推公式:dp[j] = max(dp[j], dp[j-weight(i)]+value(i))
初始化:dp[0] = 0, dp数组在推导时会选择价值最大的数,那么dp数组的非0下标只需要初始化为一个比较小的数就可以了
遍历顺序:一维数组遍历背包容量时采取的是倒序遍历,为了保证物品i只被放入一次,一旦正序遍历,dp[j]就会使用dp[j-1]这个已经更新过的值,实际上需要使用的是上一轮中的dp[j-1];另外两个嵌套for循环的顺序也必须是先遍历物品再遍历背包容量,如果反过来会导致背包中只会放一个物品。
for(int i = 0; i < weight.size(); i++) { // 遍历物品
for(int j = bagWeight; j >= weight[i]; j--) { // 遍历背包容量
dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
}
}
function BagProblem(weight, value, size){
let len = weight.length
let dp = Array(size+1).fill(0)
for(let i =0; i<len; i++){ //遍历物品
for(let j=size; j>=weight[i]; j--){ //遍历背包
dp[j] = Math.max(dp[j], dp[j-weight[i]] + value[i])
}
}
return dp[size]
}
416. 分割等和子集
要求:给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
思路
本题难点在于如何将分割等和子集问题转换为01背包问题,本题就是要将背包容量为sum/2的背包装满,判断是否dp[sum/2] == sum/2,等于则并表明可以分割成两个元素和相等的子集,且每个元素只能使用一次,所以是01背包问题,物品的价值和重量都是元素的数值。
var canPartition = function(nums) {
let sum = nums.reduce((p, v)=>p+v, 0)
if(sum % 2 == 1) return false //和为奇数
let target = sum /2
let dp = Array(target+1).fill(0)
for(let i=0; i<nums.length; i++){
for(let 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
};