代码随想录训练营第三十六天 | 1049. 最后一块石头的重量 II 494. 目标和 474.一和零

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

实际上还是01背包问题。

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

494. 目标和

其实还是01背包问题,但是却有一点不一样:

  • 相同点:将所有的数分为——正数组合、负数组合。两者之和为target。
  • 不同点:不再是求背包最多能装多少了,而是最多装多少的情况下,有多少种装法——变成了组合问题。

不同点:

  1. 和:(sum+target)/2,将target算作正数集合里的一员(甭管它是正是负)。也正因此,可以理解为,正数会被装进背包,而负数不会。这样就避免了要把所有东西都装进背包(这样不是01背包问题了)。
  2. dp含义:切合题目的含义——有多少种装法。dp[i][j]:使用 下标为[0, i]nums[i]能够凑满j(包括j)这么大容量的包,有dp[i][j]种方法。
    这里是正好装满该空间,而不是一定要达到最后条件。一开始想岔劈了。。
  3. 终止条件:有两个,详见开头的两个if判断。
  4. 初始条件:不再全为0了,因为容量为0时,也算是装满了包。
class Solution {
    public int findTargetSumWays(int[] nums, int target) {
        int sum = Arrays.stream(nums).sum();
        if((sum+target)%2==1) return 0;
        if(Math.abs(target)>sum) return 0;
        
        int size = (sum+target)/2;
        int[] dp = new int[size+1];

        // 初始条件:对于没有物品可选的时候,装满容量为0的背包,有1种方法。但是装不满其他容量的背包,其他为0。
        dp[0] = 1;

        for(int i=0; i<nums.length; i++){
            for(int k=size; k>=nums[i]; k--){
                dp[k] += dp[k-nums[i]]; // 装满该容量,有多少种方法——两种情况:不加该物品(dp[k]);加入该物品(dp[k-nums[i]])
            }
        }
        return dp[size];
    }  
}

474.一和零

class Solution {
    public int findMaxForm(String[] strs, int m, int n) {
		// 首先从参数中,区分物品与容量。
		// strs:物品,需要一个个考虑。
		// m,n:限制条件,也就是容量。
        int[][] dp = new int[m+1][n+1]; // 容量+1是考虑到容量为0的情况。

        // 初始化:初始值为0

        // 对于每个物品,遍历的每个容量
        for(int i=0; i<strs.length; i++){
            // 计算
            int count0 = 0;
            int count1 = 0;
            for(int k = 0; k < strs[i].length(); k++){
                if(strs[i].charAt(k)=='1') count1++;
                else if(strs[i].charAt(k)=='0') count0++;
            }
            // 递推
            for(int i0=m; i0>=count0; i0--){ // 限制条件的剪枝:只有装下该物品、不装该物品两种情况
                for(int i1=n; i1>=count1; i1--){
                    dp[i0][i1] = Math.max(dp[i0][i1], dp[i0-count0][i1-count1] + 1);
                }
            }
        }

        return dp[m][n];
    }
}

小小总结一下背包的公式

  1. dp含义:new int[maxSize+1] 对于每个容量都考虑到了,包括容量为0。
    同时也避免了下标与容量不匹配的情况,免得加一减一。
  2. 遍历:首先分别考虑一个个加入物品,然后再考虑加入物品之后的容量——这样很容易递推,只有两种情况,放入该物品/不放。
    因此,外循环是物品循环,内循环是容量循环(而且还是倒着遍历)。
  3. 剪枝:条件是,此时容量能否装下该物品,否则不变。
    正因此,同时也避免了内部的条件判断。
  4. 递推公式(滚动数组):就是将放入/不放入两种情况考虑。要么是dp[k],维持上一轮的结果;要么是dp[k-cost[i]] + value[i],新的结果。
    还是要注意滚动数组问题的思路:物品数量与容量都是不断增加的,由此才会滚动。

物品数量:就是将已知的元素,一个一个放入集合,然后考虑整个集合的问题。
容量:就是限制条件。本来限制条件很大,但是先将其缩小,一步一步计算。所以,dp的长度,与这个限制条件的大小有关。

可以将“474.一和零 ”作为示例。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值