PigyChan_LeetCode 1049. 最后一块石头的重量 II

1049. 最后一块石头的重量 II

有一堆石头,每块石头的重量都是正整数。
每一回合,从中选出任意两块石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y,且 x <= y。那么粉碎的可能结果如下:
*
如果 x == y,那么两块石头都会被完全粉碎;
*
如果 x != y,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x。

最后,最多只会剩下一块石头。返回此石头最小的可能重量。如果没有石头剩下,就返回 0。

示例:

输入:[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],这就是最优值。

提示:
1.
1 <= stones.length <= 30
2.
1 <= stones[i] <= 1000

思路1.0:每次优先拿两个最大石头来砸

在这里插入图片描述

但每次都要找到两个最大值消耗很高

思路2.0(看了题解):每次拿两个石头对砸,砸到最后其实就是两堆石头对砸,且使得两堆石头质量和的差的绝对值最小。
最理想的情况是质量分别为x,y的两堆石头与总质量成:xysum/2,x+y==sum
其他情况则是:x<sum/2<y或者y<sum/2<x
要使得abs(x-y)最小,则在x与y质量不等时,较小的那一份尽量大,所以这题可以转变为:从stones中取若干石头,使得其质量和在sum/2的限制下达到最大(01背包问题)。
设置vector dp(sum/2)
状态转移公式为:dp[n]=max(dp[n],dp[n-stone_wt]+stone_wt);
代码1.0

class Solution {
public:
    int lastStoneWeightII(vector<int>& stones) {
        int sum = 0;
        for (const auto& i : stones)
        {
            sum += i;
        }


        int limit = sum / 2;
        vector<int> dp(limit+1);


        for (const auto& stone : stones)
        {
            for (int j = limit; j >= stone; j--)
            {
                dp[j] = max(dp[j], dp[j - stone] + stone);
            }
        }


        return (sum - dp[limit]) - dp[limit];
    }
};

在看题解和做题的过程中,一直对“01背包”问题的“滚动数组解法”不是特别理解,494. 目标和也是如此,在此要对其定点爆破了。

“01背包与滚动数组”

示例代码:

1.     const int N = 3;//物品个数  
2.     const int V = 5;//背包最大容量  
3.     int weight[N + 1] = {0,3,2,2};//物品重量  
4.     int value[N + 1] = {0,5,10,20};//物品价值  

1.     for (int i = 1;i <= N;i++) //枚举物品  
2.     {  
3.         for (int j = V;j >= weight[i];j--) //枚举背包容量,防越界,j下限为 weight[i]  
4.         {  
5.             f[j] = Max(f[j],f[j - weight[i]] + value[i]);  
6.         }  
7.     }  
8.     return f[V];  

观察一维状态转移方程:
f[i][v] = max(f[i - 1][v],f[i - 1][v - weight[i]] + cost[i])
首先我们明确三个问题

1) v - weight[i] < v
2) 状态f[i][v] 是由 f[i - 1][v] 和 f[i - 1][v - weight[i]] 两个状态决定
3) 对于物品i,我们在枚举背包容量时,只要背包容量能装下物品i 且 收益比原来的大,就会成功放入物品i。

由此得出的几个要点:
j逆序遍历的原因:为了保证在最大的那个最优解在需要用到他对应的较小最优解时(例如i=2,j=5时的f[5],即f[2][5]=f[1][5-weight[2]]+value[2],
此时f[2][5]也就是f[5]需要f[i-1][3]=f[1][3]即 在i=1,j=3时f[3]的值)
,那个较小最优解不能被覆盖,还保留着f[i-1][j]的值。

在这里插入图片描述
在这里插入图片描述

j的值取在[weight[i],V]的原因,因为[0,weight[i]),是因为容量太小,而前i个物品肯定放不进去的情况,所以省去这些无用功。
下次再忘了就拿这个例子手动debug一次,配合这条笔记食用,就明白了

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值