【力扣-背包】2、最后一块石头的重量II(1049)

190 阅读1分钟

「这是我参与2022首次更文挑战的第5天,活动详情查看:2022首次更文挑战」。

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

题目描述

有一堆石头,用整数数组 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

示例 3:

输入: stones = [1,2]
输出: 1

解析

/**
 * @brief 背包问题
 * 本题可以看作是上一题的变体,将石头分成两堆,然后两堆相互抵消,剩下最小的石头
 * 物品的重量为 stones[i],物品的价值也为stones[i]
 * 对应01背包里的物品重量weight[i]和物品价值value[i]
 * 
 * 动规五部曲(四部曲)
 * 1、确定dp数组以及下标的含义
 *      dp[j]表示容量为j的背包,最多可以装下dp[j]重量的石头
 * 2、确定地推公式
 *      01背包的递推公式为:dp[j] = max(dp[j],dp[j-weight[i]]+value[i]);
 *  本题:
 *      dp[j] = max(dp[j],dp[j-stones[i]]+stones[i]);
 * 3、dp数组的初始化
 *      提示中给出: 1<=stones.length <=30 , 1<=stones[i]<=1000.
 *      所以最大重量为 30*1000 = 30000
 *      所以这里dp数组大小为 15000
 *      
 *      石头都是有重量的,所以可以将dp[j]全部初始化为0
 * 
 *      vector<int> dp(15000,0);
 * 4、确定遍历顺序
 *      使用一维dp数组时,物品遍历的for循环放在最外层,遍历背包的for循环放在内层,且内层for循环使用倒序
 * 
 *  
 */

代码

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

        // 将物品分成两部分
        int target = sum / 2;
        vector<int> dp(sum, 0);

        for (int i = 0; i < size; i++) // 遍历物品
        {
            for (int j = target; j >= stones[i]; j--) // 遍历背包
            {
                dp[j] = max(dp[j], dp[j - stones[i]] + stones[i]);
            }
        }
        // 因为 target 是向下取整,所以 sum - dp[target]要大于等于 dp[target]
        return sum - dp[target] - dp[target];
    }
};