106、★★01背包-动态规划-LeetCode-1049.最后一块石头的重量-模仿416分割等和子集-背公式

题目

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

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

如果 x == y,那么两块石头都会被完全粉碎;
如果 x != y,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x。
最后,最多只会剩下一块 石头。返回此石头 最小的可能重量 。如果没有石头剩下,就返回 0。

来源:力扣(LeetCode)

思路:模仿416.分割等和子集,主要是背公式 + 先脑筋急转弯理解题目想要的思路

这道题如果不是提示,几乎想不到使用DP,是01背包问题;先想到的是回溯或者递归等

1)不管是怎么取石子,最后的结果都是相互抵消的问题

如果没有完全抵消,肯定会不停的剩下石子;就一直抵消下去

抵消了就是0,不抵消最后只能剩一个!

2)题目给出了,正整数!所以如果总和为 偶数,最后就全部抵消!

如果不为偶数,最大可抵消的就是 sum / 2!

将该值作为背包容量,去取石子;看最大能取到的石子重量,就是能抵消的值

一个疑惑:打碎的石子怎么计算呢!该取法是取原来的整块,但是会出现更新更小的石子!

3)一维dp中:背包容量需要倒序遍历;为了使物品只入队一次,只有一个物品
从前往后,会出现dp[j - weight[i]]已经入队了某物品,此时又将某物品入队!因为是两层for
物品放外层,为了防止:不同情况下,物品只能入队一次

★★:dp[i][j] 只和 dp[i - 1][j] 和dp[i - 1][j - weight[i]] 有关;可以看出,只和上一行的内容有关

因此可以使用滚动数组,实现空间的优化![j - weight[i]] 只影响了列!

滚动数组

背包问题的理解,来自代码随想录

物品按顺序添加,背包按倒序遍历;

几个物品就尝试几次进入背包的操作;可以理解为五个不同容量的背包

由于每次物品进入背包会涉及前面的内容,如果背包由小到大遍历,同一个物品会多次进入背包!

容易困惑的点:还是背包中物品价值的更新问题,二维容易理解;不要钻牛角尖,其实很好捋顺!

代码

1)二维数组

class Solution {
    public int lastStoneWeightII(int[] stones) {
        //这道题,第一想法,看着像是回溯或者递归这种的!
        //如果不是按照攻略刷题很难想到是 01背包
        int n = stones.length;
        if(n == 1) return stones[0];
        //先计算总和
        int sum = 0;
        for(int i = 0;i < n;i++){
            sum += stones[i];
        }
        //类似上一道,分割等和子集;最大只能取到 一般,也就是最大只能抵消一半,此时就是剩下0
        int res = sum;
        sum = sum / 2;
        int[][] dp = new int[n + 1][sum + 1];
        for(int i = 0;i <= n;i++){
            dp[i][0] = 0;//初始化
        }
        //二维dp赋值
        for(int i = 1;i <= n;i++){
            for(int j = 1;j <= sum;j++){
                //对照dp数组,dp的i行其实就是原数组的 i - 1行
                if(j < stones[i - 1]) dp[i][j] = dp[i - 1][j];
                else 
                    dp[i][j] = Math.max(dp[i - 1][j],dp[i - 1][j - stones[i - 1]] + stones[i - 1]);
            }
        }
        //最后dp[n][sum]就是最大可消去的石头重量
        return res - 2 * dp[n][sum];
    }
}

2)一维dp数组

class Solution {
    public int lastStoneWeightII(int[] stones) {
        //这道题,第一想法,看着像是回溯或者递归这种的!
        //如果不是按照攻略刷题很难想到是 01背包
        int n = stones.length;
        if(n == 1) return stones[0];
        //先计算总和
        int sum = 0;
        for(int i = 0;i < n;i++){
            sum += stones[i];
        }
        //类似上一道,分割等和子集;最大只能取到 一般,也就是最大只能抵消一半,此时就是剩下0
        int res = sum;
        sum = sum / 2;
        int[] dp = new int[sum + 1];
     
        //一维dp赋值
        for(int i = 0;i < n;i++){
            //背包容量需要倒序遍历;为了使物品只入队一次,只有一个物品
            //从前往后,会出现dp[j - weight[i]]已经入队了某物品,此时又将某物品入队!因为是两层for
            //物品放外层,为了防止:不同情况下,物品只能入队一次
            for(int j = sum;j >= stones[i];j--){
                dp[j] = Math.max(dp[j],dp[j - stones[i]] + stones[i]);
            }
        }
        //最后dp[n][sum]就是最大可消去的石头重量
        return res - 2 * dp[sum];
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值