题目一 01背包--二维数组解法
有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。
经典问题,使用二维数组好理解,滚动数组可以节省空间
思路
-
dp[i][j]数组含义:从0-i个物品中任意取,放进容量为j的背包,价值总和最大是多少
-
递推公式:
dp[i][j] = Math.max(dp[i - 1][j], dp[i-1][j - weight[i]] + value[i])- dp[i - 1][j]代表不选第i个元素,那它的价值就等于前i-1个元素的结果,这个是第i个的重量大于j的情况,就是i元素放不进背包的情况。
- dp[i-1][j - weight[i]] 代表选择第i个物品的情况,重量要留出可以放i元素的空间,所以是i-1个元素的放进容量为j - weight[i]的背包的最大价值加上第i个元素的价值
-
初始化:i为0时,代表放第一个元素放进容量为0-j的背包的最大价值,那就是如果weight[0] < j 可以放的下的话就初始化为weight[0],否则不放物品就是0。对于j = 0,背包价值为0,那dp[i][0]都初始化为0
-
遍历顺序:外层遍历i个物品,内层遍历j个重量的背包,从前向后遍历,因为都依赖于上层的值。
function bag01(weight, value, bagweight) {
const len = weight.length;
// 二维数组:包含bagweight,所以初始化为bagweight+1个元素
const dp = new Array(len).fill(0).map(item => new Array(bagweight + 1).fill(0));
// 初始化:第一行的元素如果放的下物品0,则初始化为0的重量
for (let j = weight[0]; j <= bagweight; j++) {
dp[0][j] = value[0];
}
// 第一行已经初始化完成,所以初始化从第2行开始
for (let i = 1; i < len; i++) {
// 包含bageweight是<=
for (let j = 1; j <= bagweight; j++) {
// 如果当前背包重量放不下该元素,则直接初始化为上一行的值
if (weight[j] > j) {
dp[i][j] = dp[i-1][j];
} else {
// 如果当前背包放的下物品i,则有两种情况,一种是放i物品,一种不放i物品,取两者最大值
dp[i][j] = Math.max(dp[i-1][j], dp[i-1][j-weight[i]] + value[i])
}
}
}
// 最后一行的最后一个元素
return dp[len-1][bagweight];
}
const weight = [1, 3, 4];
const value = [15, 20, 30];
const bagweight = 4;
bag01(weight, value, bagweight);
题目二 01背包-滚动数组解法
之所以有这个解法,是因为上面的背包状态是可以压缩的。递推公式dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);只和上一层元素有关,所以每次可以重复利用元素。
-
dp[j]: 容量为j的背包,物品价值最大为dp[j]
-
递推公式:dp[j] = max(dp[j], dp[j - weight[i]] + value[i])
- dp[j]: 在没赋值前就相等于不放物品i得情况
- dp[j - weight[i]] + value[i]: 放物品i,减去这个物品重量的之前的结果再加上物品i的价值。
-
初始化:dp[0] = 0,其他也初始化为0
-
遍历顺序:外层遍历物品,内层遍历背包,并且是倒序的,因为正序的话相当于上一层的值已经被更改,后面会重复计算,物品被多次放进去。
function bag02(weight, value, bagweight) {
const len = weight.length;
// 一维数组
const dp = new Array(bagweight + 1).fill(0);
for (let i = 0; i < len; i++) {
for (let j = bagweight; j >= weight[i]; j--) {
dp[j] = Math.max(dp[j], dp[j-weight[i]] + value[i])
}
}
return dp[bagweight];
}
const weight = [1, 3, 4];
const value = [15, 20, 30];
const bagweight = 4;
bag01(weight, value, bagweight);
bag02(weight, value, bagweight);
题目三 416. 分割等和子集
给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
思路
等和子集,那就是把所有元素的和求出来除以2就得到了需要的和target,问题转化为在这个集合中找到和等于target的子集。
- dp[j]含义: 容量为j的背包,放入物品后最大的重量
- 递推公式:dp[j] = max(dp[j], dp[j - weight[i]] + value[i])
- 初始化:dp[0] = 0,元素都初始化为0
- 遍历顺序: 外层是遍历物品,内存遍历背包,内层倒序遍历
var canPartition = function(nums) {
let sum = 0;
const len = nums.length;
for (let i = 0; i < len; i++) {
sum += nums[i];
}
if (sum % 2 === 1) {
return false;
}
const target = sum / 2;
const dp = new Array(target + 1).fill(0);
for (let i = 0; i < len; i++) {
for (let j = target; j >= nums[i]; j--) {
dp[j] = Math.max(dp[j], dp[j-nums[i]] + nums[i]);
}
}
return dp[target] === target;
};