题目介绍:
上篇文章我们分析了416. 分割等和子集 - 力扣(LeetCode)这道题,今天我们再来看下01背包问题的另一道题目,看题目:
有一堆石头,用整数数组 stones
表示。其中 stones[i]
表示第 i
块石头的重量。
每一回合,从中选出任意两块石头,然后将它们一起粉碎。假设石头的重量分别为 x
和 y
,且 x <= y
。那么粉碎的可能结果如下:
- 如果
x == y
,那么两块石头都会被完全粉碎; - 如果
x != y
,那么重量为x
的石头将会完全粉碎,而重量为y
的石头新重量为y-x
。
最后,最多只会剩下一块 石头。返回此石头 最小的可能重量 。如果没有石头剩下,就返回 0
。
示例 1:
输入:stones = [2,7,4,1,8,1] 输出:1 解释: 组合 2 和 4,得到 2,所以数组转化为 [2,7,1,8,1], 组合 7 和 8,得到 1,所以数组转化为 [2,1,1,1], 组合 2 和 1,得到 1,所以数组转化为 [1,1,1], 组合 1 和 1,得到 0,所以数组转化为 [1],这就是最优值。
示例 2:
输入:stones = [31,26,33,21,40] 输出:5
简单来说,这道题就是要在一堆石头中挑两块石头对撞,若两石头质量相等,则一起粉碎;若质量不等,小的石头被粉碎,大的石头被撞碎的重量等于小石头的重量。
分析:
我们该挑选哪两块石头粉碎呢?显然,我们很难挑选,每一次选择都会对后续的选择造成影响,我们很难从中选出最优解,若全部列举出来再选最小值,时间复杂度依然很高。我们来重新审视一下它最后的要求:它要求最后剩下的石头质量最小,我们可以自然而然地想到如果剩余的两块石头质量是最接近的,是不是就说明最后剩下的石头质量最小?思路出来了,但我们怎么保证剩余下的两块石头质量最接近呢?其实我们不用保证剩最后两块石头,可以广义到剩最后两堆石头,将这两堆石头粉碎剩下的就是质量最小的石头。
看到这里,大家有没有一点熟悉的感觉?将石头分为两堆,比较其重量的差值,是不是跟我们上一题的分割等和子集一样?只不过上一题我们是看两个子集是否相等,这题是看两堆石头的差是多少,本质上是一模一样的。由此我们就可以开始解决题目了!
题解:
老样子,动规五部曲:
1、确定dp数组以及下标的含义
和分割等和子集一样,我们将石头的总重量/2视为目标数即为背包容量(我们期待最后两堆石头是一样重的,这样粉碎后就为0了),将每一块石头视为物品,物品的价值和重量都等于这个元素代表的数",所以dp[j]表示 背包总容量是j,放进物品后,背的最大重量为dp[j]
2、确认递推公式
01背包的递推公式为:dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
本题,相当于背包里放入数值,那么物品i的重量是nums[i],其价值也是nums[i]。
所以递推公式:dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]);
3、初始化dp数组
石头重量不存在负数,dp数组在推导的时候一定是取价值最大的数,如果题目给的价值都是正整数那么非0下标都初始化为0就可以了。
这样才能让dp数组在递归公式的过程中取的最大的价值,而不是被初始值覆盖了。
那么我假设物品价值都是大于0的,所以dp数组初始化的时候,都初始为0就可以了。
4、确定遍历顺序
使用一维dp数组,物品遍历的for循环放在外层,遍历背包的for循环放在内层,且内层for循环倒序遍历!如果内层for循环正序遍历,那么前面的值会持续影响后续的值,即影响效果是叠加的。可以手动算下就可以理解这个意思了
for(int i=0;i<stones.size();i++){ // 每一个元素一定是不可重复放入,所以从大到小遍历
for(int j=weight;j>=stones[i];j--){
dp[j]=max(dp[j],dp[j-stones[i]]+stones[i]);
}
}
5、举例推导dp数组
手动算下并和打印数组比对
最后附上源码:
int lastStoneWeightII(vector<int>& stones) {
int sum=0;
for(int i=0;i<stones.size();i++){
sum+=stones[i];
}
int weight=sum/2;
vector<int>dp(weight+1,0);
for(int i=0;i<stones.size();i++){
for(int j=weight;j>=stones[i];j--){
dp[j]=max(dp[j],dp[j-stones[i]]+stones[i]);
}
}
int lastWeight=abs(sum-2*dp[weight]);
return lastWeight;
}
总的来说,本题和上题本质上是一样的,只不过要能将题目意思转化一下,这两题都是较简单的01背包问题,后续的题会更有挑战一些,下一篇博客我会讲494. 目标和 - 力扣(LeetCode),感兴趣的可以先做一下