代码随想录训练营day43, 最后一块石头的重量, 目标和, 一和零

最后一块石头的重量

有一堆石头, 从中任选两块石头, 将他们一起粉碎, 返回此石头可能的最小重量

思路: 就是尽量让石头分成重量相同的两堆, 相撞之后剩下的石头最小, 这里的重量和价值都为stone[i]

  1. dp[j]数组: 表示容量为j的背包, 最多可以背dp[j]这么重的石头
  2. 递推公式: 和分割等和子集是一模一样的
  3. 初始化: 仍然是石头总重的一半
  4. 遍历顺序: 如果用一维dp, 那么遍历和之前都一样

根据代码发现前面全部都是一模一样的, 只不过最后的return不同, 就是总重-粉碎后的重量

class Solution {
    public int lastStoneWeightII(int[] stones) {
        //这里题目说清楚了, 所以我们不需要剪枝了
        int sum = 0;
        for(int stone : stones){
            sum += stone;
        }
        int len = stones.length;
        //仍然是定义target
        //因为最后算结果的时候还会*2, 所以抵消了, 不用判断奇数
        int target = sum/2;
        int[] dp = new int[target + 1];
        for(int i = 0; i < len; i++){
            for(int j = target; j >= stones[i]; j--){
                dp[j] = Math.max(dp[j], dp[j - stones[i]] + stones[i]);
            }
        }
        
        return sum - dp[target]*2;
    }
}

目标和

这道题其实是有点像回溯的组合总和的, 所以一样可以用回溯法解决, 不过也会超时

他这里也是分成两个集合, 一个加法, 一个减法, 所以也用完全背包: 加法集是left, 减法集是right, left+right=sum,就是方法总和; left-right=target就是目标数, 所以可以发现 left = (target + sum)/2(这里如果除2整除不了的话, 就说明找不到, 直接return0)

  1. 装满容量为j的背包, 有dp[j]种方法
  2. 递推公式: dp[j - num[i]]推出dp[j] (比如说dp[5], 已存在1的话, 还要dp[4]推导)
  3. 初始化: dp[0]=1, 就是装满容量为0的背包, 就有一种方发—不装
  4. 遍历顺序就不废话了

细节:

  1. 注意这里的剪枝, 不能被整除, 则return0
  2. 定义size用来做容器
  3. 这里一定要初始化的
  4. 递推的for循环第二个j是从size开始-, 大于nums[i]
class Solution {
    public int findTargetSumWays(int[] nums, int target) {
        int sum = 0;
        for(int i = 0; i < nums.length; i++){
            sum += nums[i];
        }
        //剪个枝
        //之前推导的公式,不能被整除, 就说明没有组合
        if((target + sum) % 2 != 0) return 0;
        //定义size用来作为left容器
        int size = (target + sum) / 2;
        //因为现在只用正数做容器, 所以碰到负数就return
        if(size < 0) return 0;
        int[] dp = new int[size + 1];
        //初始化数组
        dp[0] = 1;
        //开始递推, z这里有细节
        for (int i = 0; i < nums.length; i++) {
            for (int j = size; j >= nums[i]; j--) {
                dp[j] += dp[j - nums[i]];
            }
        }
        return dp[size];
    }
}

一和零

这个背包有两个维度, 一个是m, 一个是n, 而不同长度的字符串就是不同大小的待装物品

  1. dp[i][j], 最多有i个0和j个1的strs的最大自己的大小为dp[i][j]
  2. 递推公式: dp[i][j]有上一个字符串推导出来, str中有x个0, y个1: dp[i - x][j - y] + 1 (解释上一个, 就是没放x和y, 然后加上1)
  3. 初始化: 初始化为0就行
  4. 遍历顺序: 这里两个有两个维度, 两个其实都是背包, 所以都要倒序

(注意: 这里一开始要统计0和1的数量; 由于他这里是字符串, 所以为了遍历单个字符串, 还需要一个打的for循环)

class Solution {
    public int findMaxForm(String[] strs, int m, int n) {
        int[][] dp = new int[m+1][n+1];
        int oneNum, zeroNum;
        for(String str : strs){ //这里是遍历物品
            //定义一下为零的数和一的数
            oneNum = 0;
            zeroNum = 0;
            for(char ch : str.toCharArray()){
                //计算0和1的数量
                if(ch == '0'){
                    zeroNum++;
                } else {
                    oneNum++;
                }
            }
            //然后开始倒序遍历
            for (int i = m; i >= zeroNum; i--) {
                for (int j = n; j >= oneNum; j--) {
                    dp[i][j] = Math.max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1);
                }
            }
            
        }
        return dp[m][n];

    }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值