提示:努力生活,开心、快乐的一天
文章目录
1049. 最后一块石头的重量 II
💡解题思路
- 本题其实就是尽量让石头分成重量相同的两堆,相撞之后剩下的石头最小,这样就化解成01背包问题了。
- 动规五部曲:
- 确定dp数组以及下标的含义:dp[j]表示容量(这里说容量更形象,其实就是重量)为j的背包,最多可以背最大重量为dp[j]。
- 确定递推公式:01背包的递推公式为:dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);本题则是:dp[j] = max(dp[j], dp[j - stones[i]] + stones[i]);
- dp数组如何初始化:因为重量都不会是负数,所以dp[j]都初始化为0就可以了,这样在递归公式dp[j] = max(dp[j], dp[j - stones[i]] + stones[i]);中dp[j]才不会初始值所覆盖。
- 确定遍历顺序:如果使用一维dp数组,物品遍历的for循环放在外层,遍历背包的for循环放在内层,且内层for循环倒序遍历!
- 举例推导dp数组:按照递推公式推导一下做推导,如果发现结果不对,就把dp数组打印出来
分成两堆石头,一堆石头的总重量是dp[target],另一堆就是sum - dp[target]。
在计算target的时候,target = sum / 2 因为是向下取整,所以sum - dp[target] 一定是大于等于dp[target]的。
那么相撞之后剩下的最小石头重量就是 (sum - dp[target]) - dp[target]
🤔遇到的问题
- 分两堆石头的时候,没有向下取整
💻代码实现
动态规划
var lastStoneWeightII = function (stones) {
let sum = stones.reduce((a, b) => a + b)
//向下取整,总和为5,取2,所以sum-dp[dplen]>=dp[dpLen]
let dpLen = Math.floor(sum / 2)
let dp = new Array(dpLen + 1).fill(0)
for (let i = 0; i < stones.length; i++) {
for (let j = dpLen; j >= stones[i]; --j) {
dp[j] = Math.max(dp[j], dp[j - stones[i]] + stones[i])
}
}
//两个相近的集合求查差值
return sum - dp[dpLen] - dp[dpLen]
};
🎯题目总结
本题其实和416. 分割等和子集 (opens new window)几乎是一样的,只是最后对dp[target]的处理方式不同。
416. 分割等和子集 (opens new window)相当于是求背包是否正好装满,而本题是求背包最多能装多少。
494. 目标和
💡解题思路
- 因为每个物品(题目中的1)只用一次!
这次和之前遇到的背包问题不一样了,之前都是求容量为j的背包,最多能装多少。 - 假设加法的总和为x,那么减法对应的总和就是sum - x。
所以我们要求的是 x - (sum - x) = target
x = (target + sum) / 2
此时问题就转化为,装满容量为x的背包,有几种方法。
这里的x,就是bagSize,也就是我们后面要求的背包容量。
(target + sum) / 2 不能有余数,有余数的话,算是无解 - 动规五部曲:
- 确定dp数组以及下标的含义:dp[j] 表示:填满j(包括j)这么大容积的包,有dp[j]种方法
- 确定递推公式:只要搞到nums[i],凑成dp[j]就有dp[j - nums[i]] 种方法。例如:dp[j],j 为5,
1、已经有一个1(nums[i]) 的话,有 dp[4]种方法 凑成 容量为5的背包。
2、已经有一个2(nums[i]) 的话,有 dp[3]种方法 凑成 容量为5的背包。
3、已经有一个3(nums[i]) 的话,有 dp[2]中方法 凑成 容量为5的背包
4、已经有一个4(nums[i]) 的话,有 dp[1]中方法 凑成 容量为5的背包
5、已经有一个5 (nums[i])的话,有 dp[0]中方法 凑成 容量为5的背包
那么凑整dp[5]有多少方法呢,也就是把 所有的 dp[j - nums[i]] 累加起来。
递推公式为:dp[j] += dp[j - nums[i]] - dp数组如何初始化: dp[0] 为 1
- 确定遍历顺序:01背包问题一维dp的遍历,nums放在外循环,target在内循环,且内循环倒序。
- 举例推导dp数组:按照递推公式推导一下做推导,如果发现结果不对,就把dp数组打印出来
🤔遇到的问题
- 递推公式的获取非常的难思考到
💻代码实现
动态规划
var findTargetSumWays = function(nums, target) {
const sum = nums.reduce((a, b) => a+b);
if(Math.abs(target) > sum) {
return 0;
}
if((target + sum) % 2) {
return 0;
}
const halfSum = (target + sum) / 2;
let dp = new Array(halfSum+1).fill(0);
dp[0] = 1;
for(let i = 0; i < nums.length; i++) {
for(let j = halfSum; j >= nums[i]; j--) {
dp[j] += dp[j - nums[i]];
}
}
return dp[halfSum];
};
🎯题目总结
本题还是有点难度,大家也可以记住,在求装满背包有几种方法的情况下,递推公式一般为:
dp[j] += dp[j - nums[i]];
474. 一和零
💡解题思路
- 依旧是01背包问题
- 动规五部曲:
- 确定dp数组以及下标的含义:dp[i][j]:最多有i个0和j个1的strs的最大子集的大小为dp[i][j]。
- 确定递推公式:dp[i][j] 可以由前一个strs里的字符串推导出来,strs里的字符串有zeroNum个0,oneNum个1。
dp[i][j] 就可以是 dp[i - zeroNum][j - oneNum] + 1。
然后我们在遍历的过程中,取dp[i][j]的最大值。
所以递推公式:dp[i][j] = max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1); - dp数组如何初始化: dp[0][0] 为 0 - 确定遍历顺序:01背包问题一维dp的遍历,nums放在外循环,target在内循环,且内循环倒序。
- 举例推导dp数组:按照递推公式推导一下做推导,如果发现结果不对,就把dp数组打印出来
🤔遇到的问题
- 容易搞混i,j的含义,m,n为两个背包
💻代码实现
动态规划
var findMaxForm = function(strs, m, n) {
const dp = Array.from(Array(m+1), () => Array(n+1).fill(0));
let numOfZeros, numOfOnes;
for(let str of strs) {//遍历物品(字符串),统计0,1的个数
numOfZeros = 0;
numOfOnes = 0;
for(let c of str) {
if (c === '0') {
numOfZeros++;
} else {
numOfOnes++;
}
}
for(let i = m; i >= numOfZeros; i--) {//遍历书包容量(从后向前),0,1的个数就是背包的容量
for(let j = n; j >= numOfOnes; j--) {
dp[i][j] = Math.max(dp[i][j], dp[i - numOfZeros][j - numOfOnes] + 1);
}
}
}
return dp[m][n];
};
🎯题目总结
01背包问题大汇总
- 纯 0 - 1 背包 (opens new window)是求 给定背包容量 装满背包 的最大价值是多少。
-
- 分割等和子集 (opens new window)是求 给定背包容量,能不能装满这个背包。
-
- 最后一块石头的重量 II (opens new window)是求 给定背包容量,尽可能装,最多能装多少
-
- 目标和 (opens new window)是求 给定背包容量,装满背包有多少种方法。
- 本题是求 给定背包容量,装满背包最多有多少个物品。
🎈今日心得
背包问题的拓展,好难