- 01背包问题,你该了解这些!
题目链接:46. 携带研究材料(第六期模拟笔试)
背包问题:dp[i][j]表示从 0-i 的物品中任意取,放进容量为 j 的背包,最大的价值和;
看了解析后总结背包问题的思路:
1)含义:dp[i][j]表示从 0-i 的物品中任意取,放进容量为 j 的背包,最大的价值和; 遍历 0-i 的物品,判断放入该物品的最大值, 遍历所有的背包容量情况(j), 判断当前 dp[i][j[的值如果 当前背包的容量小于物品的重量。则不放入第 i 个物品
2)递推公式:
如果不放入第 i 个物品,则 dp[i][j] = dp[i - 1][j];
如果放入第 i 个物品,那 dp[i][j] = dp[i - 1][j - weight[i]] + value[i]
3)初始化
dp[0][j]> weight[0]的时候,也就是第一个物品能被放下的时候,最大值就取第一个物品的价值,因为每个物品只能放一次,所以之后的初始化的值也是这个第一个物品的价值
4)递推方法,这里先遍历物品,再遍历容量;其实都可以
代码实现:
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
const inputLines = [];
rl.on('line', (line) => {
inputLines.push(line);
if (inputLines.length === 3) {
rl.close();
}
});
rl.on('close', () => {
processInput(inputLines);
});
function materialChoose(weight, value, size) {
let len = weight.length
let dp = new Array(len).fill().map(() => new Array(size+1).fill(0))
// i是第 i 个材料,j 是背包的容量
for(let j = weight[0]; j <= size; j = j +1) {
dp[0][j] = value[0]
}
for(let i =1; i < len; i = i + 1) {
for(let j = 0; j <= size; j = j + 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[len - 1][size]
}
function processInput(lines) {
let newArray = []
lines.forEach((line) => {
newArray.push(line.split(' ').map(item => Number(item)))
})
console.log(materialChoose(newArray[1], newArray[2], newArray[0][1]))
}
- 01背包问题,你该了解这些! 滚动数组
题目链接:46. 携带研究材料(第六期模拟笔试)和上题相同
思路:dp可以由 dp[i][j]变成 dp[j],把 dp[i- 1] 拷贝到 dp [i]层,表达式完全可以是 dp[i][j] = max(dp[j], dp[j - weight[i]] + value[i]),;
为什么要倒序?因为下一个容量依赖上一个容量,如果不是倒序的话,会多次算进上一个容量的最大值;
为什么需要先遍历物品?因为一个物品只能放进去一次
代码如下:
function materialChoose(weight, value, size) {
let len = weight.length
let dp = new Array(size + 1).fill(0)
for(let i = 0; i < weight.length; i = i + 1) {
for(let j = size; j >= weight[i]; j = j - 1) {
dp[j] = Math.max(dp[j - weight[i]] + value[i], dp[j])
}
}
console.log(dp)
return dp[size]
}
- 416. 分割等和子集
题目链接:. - 力扣(LeetCode)
首先如果要等和,这个和肯定是所有数的 sum/2
然后要将将这个和为 sum/2 的背包装满;
用一维滚动数组来解决:
代码如下:
var canPartition = function(nums) {
let sum = nums.reduce((accu, curr) => accu + curr, 0) / 2
if(sum % 1 !== 0) {
return false
}
let dp = new Array(sum + 1).fill(0)
for(let i = 0; i < nums.length; i = i + 1) {
for(j = sum; j >= nums[i]; j = j - 1 ) {
dp[j] = Math.max(dp[j], dp[j - nums[i]] + nums[i])
}
}
return dp[sum] === sum
};