01背包问题-最后一块石头的重量II

题目介绍:

上篇文章我们分析了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),感兴趣的可以先做一下

  • 22
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值