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

这篇具有很好参考价值的文章主要介绍了LeetCode1049. 最后一块石头的重量 II。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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


一、题目

有一堆石头,用整数数组 stones 表示。其中 stones[i] 表示第 i 块石头的重量。

每一回合,从中选出任意两块石头,然后将它们一起粉碎。假设石头的重量分别为 xy,且 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 <= stones.length <= 30
  • 1 <= stones[i] <= 100

二、题解

方法一:01背包二维数组
算法思路

01背包问题回顾

在01背包问题中,我们有一组物品,每个物品有两个属性:重量和价值。背包有一个固定的容量,我们的目标是在不超过背包容量的情况下,选择物品放入背包,使得放入的物品总价值最大。

我们可以将这个问题的状态定义为 dp[i][j],表示在前 i 个物品中,背包容量为 j 的情况下,可以获得的最大价值。状态转移方程可以表示为:

dp[i][j] = max(dp[i-1][j], dp[i-1][j-weight[i]] + value[i])

将题目映射到01背包

现在我们回到题目中,虽然题目描述中没有直接提到背包,但我们可以通过观察发现类似的特性:我们要将石头分成两堆,使得两堆的重量差尽量小。

在01背包问题中,我们选择物品放入背包的状态是离散的:要么放入,要么不放入。在本题中,我们可以类比,将石头看作是我们要选择放入背包的“物品”,每块石头的重量看作是物品的“重量”。我们要将石头分成两堆,使得两堆的重量差尽量小,相当于在一个背包的容量为总重量的一半时,选择一些石头放入背包,使得背包中的石头总重量尽量接近总重量的一半。

(这里的背包容量就对应着总重量的一半,而每块石头的重量和价值相同)。这就是为什么我们能够将这个问题映射到01背包问题。

具体实现
  1. 状态定义: 定义一个二维数组 dp[i][j],表示在前 i 块石头中,能否找到一种分法,使得其中一组的总重量恰好为 j。这里 i 的范围是从 0 到石头的总数,j 的范围是从 0 到总重量的一半(因为我们要将石头分成两组,两组的重量和不能超过总重量的一半,否则不符合题意)。

  2. 状态转移: 对于每一块石头,我们可以选择将其放入其中一组,或者不放入。如果我们不放入第 i 块石头,那么问题就转化为在前 i-1 块石头中寻找一种分法,使得其中一组的总重量恰好为 j。如果我们放入第 i 块石头,那么问题就转化为在前 i-1 块石头中寻找一种分法,使得其中一组的总重量恰好为 j - stones[i]

    综合考虑这两种情况,我们可以得到状态转移方程:

    dp[i][j] = dp[i-1][j] || dp[i-1][j-stones[i]]
    
  3. 边界条件: 初始化时,当只有一块石头可选时,如果这块石头的重量不超过 j,那么我们可以将其放入其中一组,否则不放入。

  4. 最终结果: 最终的答案应该是在所有可能的总重量 j 中,找到最大的 j,使得 dp[n-1][j]truen 为石头的总数)。然后最小可能的剩余重量就是 sum - 2 * j

根据上述思路,可以实现出解题代码:

class Solution {
public:
    int lastStoneWeightII(vector<int>& stones) {
        int sum = 0;
        for (int i = 0; i < stones.size(); i++) {
            sum += stones[i];
        }
        
        int n = stones.size();
        vector<vector<int>> dp(n, vector<int>(sum / 2 + 1, 0));
        
        // 初始化
        for (int i = 0; i <= sum / 2; i++) {
            if (stones[0] <= i) {
                dp[0][i] = stones[0];
            }
        }
        
        // 填写dp数组
        for (int i = 1; i < n; i++) {
            for (int j = 1; j <= sum / 2; j++) {
                if (stones[i] > j) {
                    dp[i][j] = dp[i - 1][j];
                } else {
                    dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - stones[i]] + stones[i]);
                }
            }
        }
        
        return sum - 2 * dp[n - 1][sum / 2];
    }
};
方法二:01背包一维数组
class Solution {
public:
    int lastStoneWeightII(vector<int>& stones) {
        int sum = 0;
        for (int i = 0; i < stones.size(); i++) {
            sum += stones[i];
        }
        
        vector<int> dp(sum/2+1, 0);
        // 填写dp数组
        for (int i = 0; i < stones.size(); i++) {
            for (int j = sum/2; j >= stones[i]; j--) {  
                dp[j] = max(dp[j], dp[j-stones[i]] + stones[i]);
            }        
        }
        return sum - 2 * dp[sum/2];
    }
};

Q:为什么 for (int j = sum/2; j >= stones[i]; j–)要倒序遍历?

A:我们从前往后遍历石头,同时从总重量的一半开始递减遍历,这是因为我们想在填写 dp[j] 时,基于之前的状态 dp[j-stones[i]] 进行更新。如果我们从小到大遍历 j,那么在填写 dp[j] 时,我们可能会使用当前石头的重量(stones[i]),而这就会导致重复使用同一块石头,与题意不符。

所以,倒序遍历 j 可以确保在填写 dp[j] 时,我们只会考虑之前的状态,而不会用到当前石头。这是为了避免在填写 dp[j] 时,使用当前石头导致重复计算的情况。

Q:为什么一定要先遍历石头重量这一行然后遍历重量那一列?

A:这是为了确保状态转移方程的正确性。让我们通过一个例子来理解。

假设我们有以下石头的重量:stones = [2, 7, 4]

我们想要使用动态规划找到一种分法,使得其中一组的总重量尽量接近总重量的一半。在此例中,总重量是 2 + 7 + 4 = 13,所以我们希望找到一种分法,使得其中一组的重量接近 13 / 2 = 6

现在,假设先遍历重量(j),再遍历石头(i)。在这种情况下,第一次遍历(j = sum/2,i从0到stones.size())后我们的动态规划状态数组如下所示:

stones = [2, 7, 4]
dp[i][j]:
         0   1   2   3   4   5   6      
  2:     0   0   0   0   0   0   4      
  7:     0   0   0   0   0   0   4      
  4:     0   0   0   0   0   0   4      

在这种遍历顺序下,最后一列一直到最后都不会再更新了,显然是一个错误的遍历顺序。文章来源地址https://www.toymoban.com/news/detail-686926.html

到了这里,关于LeetCode1049. 最后一块石头的重量 II的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • ( 背包问题) 1049. 最后一块石头的重量 II ——【Leetcode每日一题】

    难度:中等 有一堆石头,用整数数组 stones 表示。其中 stones[i] 表示第 i 块石头的重量。 每一回合,从中选出 任意两块石头 ,然后将它们一起粉碎。假设石头的重量分别为 x 和 y ,且 x = y 。那么粉碎的可能结果如下: 如果 x == y ,那么两块石头都会被完全粉碎; 如果 x !=

    2024年02月08日
    浏览(33)
  • Day43|leetcode 1049.最后一块石头的重量II、494.目标和、474.一和零

    题目链接:1049. 最后一块石头的重量 II - 力扣(LeetCode) 视频链接:动态规划之背包问题,这个背包最多能装多少?LeetCode:1049.最后一块石头的重量II_哔哩哔哩_bilibili 有一堆石头,每块石头的重量都是正整数。 每一回合,从中选出任意两块石头,然后将它们一起粉碎。假设

    2024年02月10日
    浏览(35)
  • [Leetcode] 416. 分割等和子集、1049. 最后一块石头的重量 II、494. 目标和、474. 一和零

    内容:今天复习下dp数组中的背包问题 分割等和子集 - 能否装满 最后一块石头 - 尽可能装满 目标和 - 有多少种方法装 一和零 - 装满背包有多少个物品 416. 分割等和子集 10背包:用/不用;有容量;有价值 dp[j] : 容量为j,最大价值为dp[j]         重量和价值等价 dp[target] == t

    2024年02月16日
    浏览(29)
  • Leet code1049 最后一块石头的重量II

    1049 最后一块石头的重量II 【问题描述】 有一堆石头,用整数数组 stones 表示。其中 stones[i] 表示第 i 块石头的重量。 每一回合,从中选出 任意两块石头 ,然后将它们一起粉碎。假设石头的重量分别为 x 和 y ,且 x = y 。那么粉碎的可能结果如下: 如果 x == y ,那么两块石头

    2024年02月13日
    浏览(30)
  • 【LeetCode题目详解】第九章 动态规划 part05 1049. 最后一块石头的重量 II 494. 目标和 474.一和零(day43补)

    有一堆石头,用整数数组  stones 表示。其中  stones[i] 表示第 i 块石头的重量。 每一回合,从中选出 任意两块石头 ,然后将它们一起粉碎。假设石头的重量分别为  x 和  y ,且  x = y 。那么粉碎的可能结果如下: 如果  x == y ,那么两块石头都会被完全粉碎; 如果  x != y

    2024年02月09日
    浏览(26)
  • 代码随想录Day36 动态规划05 LeetCode T1049最后一块石头的重量II T494 目标和 T474 一和零

    理论基础  : 代码随想录Day34 LeetCode T343整数拆分 T96 不同的二叉搜索树-CSDN博客 1.明白dp数组的含义 2.明白递推公式的含义 3.初始化dp数组 4.注意dp数组的遍历顺序 5.打印dp数组排错 题目链接:1049. 最后一块石头的重量 II - 力扣(LeetCode) 这题我们仍然采用动规五部曲来写,这题和

    2024年02月06日
    浏览(27)
  • day43 | 1049. 最后一块石头的重量 II、494. 目标和、474.一和零

    目录: 1049. 最后一块石头的重量 II 有一堆石头,用整数数组  stones  表示。其中  stones[i]  表示第  i  块石头的重量。 每一回合,从中选出 任意两块石头 ,然后将它们一起粉碎。假设石头的重量分别为  x  和  y ,且  x = y 。那么粉碎的可能结果如下: 如果  x == y ,那

    2024年02月12日
    浏览(26)
  • 算法训练第四十三天|1049. 最后一块石头的重量 II 、494. 目标和、474.一和零

    题目链接:1049. 最后一块石头的重量 II 参考:https://programmercarl.com/1049.%E6%9C%80%E5%90%8E%E4%B8%80%E5%9D%97%E7%9F%B3%E5%A4%B4%E7%9A%84%E9%87%8D%E9%87%8FII.html 题目难度:中等 有一堆石头,每块石头的重量都是正整数。 每一回合,从中选出任意两块石头,然后将它们一起粉碎。假设石头的重量分

    2023年04月09日
    浏览(27)
  • Day43|动态规划part05: 1049. 最后一块石头的重量 II、494. 目标和、474. 一和零

    本题物品的重量为stones[i],物品的价值也为stones[i]。 对应着01背包里的物品重量weight[i]和 物品价值value[i]。 确定dp数组以及下标的含义 dp[j]表示容量(这里说容量更形象,其实就是重量)为j的背包,最多可以背最大重量为dp[j] 。 确定递推公式 01背包的递推公式为:dp[j] = ma

    2024年04月23日
    浏览(33)
  • 力扣:416. 分割等和子集 & 1049. 最后一块石头的重量 II (动态规划)(二合一,一次吃透两道题)

    力扣:416. 分割等和子集 1049. 最后一块石头的重量 II 用的方法都是01背包解法,思路也是近乎一样,这里就放在一起讲解了(主要讲解第一题,第二题大家可以直接自己AC)。01背包解法详细讲解请见上篇博客01背包问题(二) 给你一个 只包含正整数 的 非空 数组 nums 。请你判断

    2024年01月20日
    浏览(37)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包