最后一块石头的重量II——01背包系列

题意

1049 最后一块石头的重量 II

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

每一回合,从中选出任意两块石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y,且 x <= y。那么粉碎的可能结果如下:

  • 如果 x == y,那么两块石头都会被完全粉碎;

  • 如果 x != y,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x。

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

提示:

  • 1 <= stones.length <= 30

  • 1 <= stones[i] <= 100

思路分析

本题和 分割等和子集 实际上一模一样。在分割等和子集中,是将给定的nums数组分为两个子集,使得两个子集中各自元素和相等,它求的是是否能把这个 容量为nums数组元素和的一半的背包 “装满”。那为什么说本题和分割等和子集一模一样?

要求得最后一块石头的重量且该重量是最优(小)值,实际上就是要将石头分成质量 尽量接近或者相等 的两堆:

从图中可看到,12 为数组stone的所有元素和的一半,即中间值,如果第一堆石头重量和与第二堆石头重量和相等,那么n == sum/2,若不相等,则n会在sum/2的两侧,那么n 与sum/2的差值即为最后一块石头的重量,求得的差值的最小值即为最优值,换言之,如果在stone数组中选取石头使得这些被选取的石头的重量之和 最接近或者等于 sum/2,那么n与sum/2的差值即为题目所求

有一点需要注意的是,由于当stone数组的元素和为奇数时,除2折半后为向下取整,如上图的例子中求得sum=23,则sum/2 = 11,那么在求答案时,2 × dp[ sum/2 ] < sum,故而最后返回sum - 2*dp[sum/2],即相当于 (sum - dp[sum/2]) - dp[sum/2],第二个等号左边代表一堆石头的重量和、右边代表一堆石头的重量和。

因此本题和分割等和子集是一样的,只不过在分割等和子集中求的是 是否能装满背包,本题求的是 背包最多能装多少

动态规划五部曲

确定dp数组含义

dp[ j ]代表从stone数组中选择石头,所选取的石头的重量和的最大值。细讲则为:从stone[0] ~ stone[i]中选取石头放入重量限重为 j 的容器中,容器中石头的重量和的最大值为 dp[j]。

确定递推公式

和分割等和子集类似,本题中 物品的重量weight和价值value 都为 石头的重量,01背包中各个量的对应关系在分割等和子集中已经详细解说,此不赘述。这里石头的重量 与 容器的限重 相对应的、石头的 重量 与dp数组的含义 也是相对应的。

dp[j] = Math.max(dp[j], dp[j - stones[i]] + stones[i]);

初始化dp数组

本题使用一维dp数组,因此可以不必初始化。

遍历顺序

遍历顺序与01背包相同,使用一维数组情况下,先遍历物品再遍历背包,即先遍历石头stone数组再遍历容器,且容器的遍历顺序是从大到小倒叙的。

for (int i = 0; i < stones.length; i++) {
    for (int j = target; j > 0; j--) {
        if (j >= stones[i]) {
            dp[j] = Math.max(dp[j], dp[j - stones[i]] + stones[i]);
        }
    }
}

举例推导dp数组

举例中每个列出来的数组代表每次遍历完容器(背包)后dp数组的情况。

完整Java代码实现

class Solution {
    public int lastStoneWeightII(int[] stones) {
        int sum = 0;
        for (int n : stones) sum += n;
        int target = sum / 2;
        int[] dp = new int[sum + 1];
        for (int i = 0; i < stones.length; i++) {
            for (int j = target; j > 0; j--) {
                if (j >= stones[i]) {
                    dp[j] = Math.max(dp[j], dp[j - stones[i]] + stones[i]);
                }
            }
        }
        return sum - 2 * dp[target];
    }
}

《代码随想录》刷题记

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值