代码随想录算法训练营 Day 43 | 1049.最后一块石头的重量 II,494.目标和,474.一和零

文章详细介绍了如何使用动态规划解决背包问题,包括01背包和完全背包,讲解了dp数组的意义、初始化、递推公式,以及如何通过举例验证这些概念。并给出了具体代码实现,如1049.最后一块石头的重量II和494.目标和问题的解决方案。
摘要由CSDN通过智能技术生成

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

讲解链接:代码随想录-1049.最后一块石头的重量 II

  1. 确定 dp 数组以及下标的含义:dp[j]表示容量(这里说容量更形象,其实就是重量)为 j 的背包,最多可以背最大重量为 dp[j]。
    石头的重量是 stones[i],石头的价值也是 stones[i] ,可以 “最多可以装的价值为 dp[j]” == “最多可以背的重量为 dp[j]”
  2. 确定递推公式:dp[j] = max(dp[j], dp[j - stones[i]] + stones[i]);
  3. dp 数组如何初始化:既然 dp[j]中的 j 表示容量,那么最大容量(重量)是多少呢,就是所有石头的重量和。而我们要求的 target 其实只是最大重量的一半。计算出石头总重量 然后除 2,得到 dp 数组的大小。
  4. 确定遍历顺序:物品遍历的 for 循环放在外层,遍历背包的 for 循环放在内层,且内层 for 循环倒序遍历!
  5. 举例推导 dp 数组:举例,输入:[2,4,1,1],此时 target = (2 + 4 + 1 + 1)/2 = 4 ,dp 数组状态图如下:
    在这里插入图片描述
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 j = target; j >= stones[i]; j--) {
            dp[j] = Math.max(dp[j], dp[j - stones[i]] + stones[i]);
        }
    }
    return sum - dp[target] - dp[target];
}

494.目标和

讲解链接:代码随想录-494.目标和

动态规划-01 背包

我们可以把集合分成两个,一个用来存放正数的,一个用来存放负数的。

正数集合和 减去 负数集合和 = 目标值。那么 正数集合和 = (目标值 + 总集合和) / 2; 推导过程就不放在这里了。

此时我们只需要让集合中的数可以凑出正数集合和,就说明是一种方法。

  1. 确定 dp 数组以及下标的含义:dp[j] 表示:填满 j(包括 j)这么大容积的包,有 dp[j]种方法

  2. 确定递推公式: 只要搞到 nums[i],凑成 dp[j]就有 dp[j - nums[i]] 种方法。

    1. 举例:nums : [1, 2, 3, 4, 5] ; target : 5 。
    2. 解释:这里 dp 的数组长度为 11,下标到 10,说明只有正数集合和为 10 的时候,才能满足目标值。所以 dp[10] 的值就是集合中的数能组成和为 10 的组合数量。
    3. 打印 :
    4.                   dp下标	 0,1,2,3,4,5,6,7,8,9,10
       i : 0, nums[i] : 1, dp : [1,1,0,0,0,0,0,0,0,0,0] 因为nums[0] = 1,所以这个集合和等于 1 的组合只有 1 种,所以 dp[1] = 1;
       i : 1, nums[i] : 2, dp : [1,1,1,1,0,0,0,0,0,0,0] nums[1] = 2,此时能组成 2 的组合有 1 种,和为 3 的集合有 1 种[1,2]。所以 dp[2] = 1, dp[3] = 1。
       i : 2, nums[i] : 3, dp : [1,1,1,2,1,1,1,0,0,0,0] nums[2] = 3,此时组成 3 的组合多了 1 种,最大能组成的和到 6。
       i : 3, nums[i] : 4, dp : [1,1,1,2,2,2,2,2,1,1,1] 下面依次类推。
       i : 4, nums[i] : 5, dp : [1,1,1,2,2,3,3,3,3,3,3]
      
    5. 上面的举例主要验证了公式和的正确性,那么为什么递推公式是:dp[j] += dp[j - nums[i]];
    6. 当背包容量(集合和)为 10 的时候,当前物品的价值为 4 时,我们有的组合就是:背包容量为 6 的组合数。
      当背包容量(集合和)为 10 的时候,当前物品的价值为 5 时,我们有的组合就是:背包容量为 5 的组合数 加上 之前已经得到的组合数。
public int findTargetSumWays(int[] nums, int target) {
    int sum = Arrays.stream(nums).sum();
    if ((target + sum) % 2 == 1 || Math.abs(target) > sum) {
        return 0; // 此时没有方案
    }
    int bagSize = (target + sum) / 2;
    int[] dp = new int[bagSize + 1];
    dp[0] = 1;
    for (int i = 0; i < nums.length; i++) {
        for (int j = bagSize; j >= nums[i]; j--) {
            dp[j] += dp[j - nums[i]];
        }
    }
    return dp[bagSize];
}
回溯算法(会超时)

提示回溯,但是用回溯会超时,这个回溯方法可以只求正数集合和来简化一下。

class FindTargetSumWays {
    public static void main(String[] args) {
        int[] num = ArrayUtils.initArray(0, 0, 0, 0, 0, 0, 0, 0, 1);
        int target = 1;
        int targetSumWays = new FindTargetSumWays().findTargetSumWays(num, target);
        System.out.println(targetSumWays);
    }


    Integer count = 0;

    public int findTargetSumWays(int[] nums, int target) {
        backtracking(nums, target, 0, new ArrayList<>());
        return count;
    }

    void backtracking(int[] nums, int target, int start, List<Integer> paths) {
        if (paths.size() == nums.length) {
            if (paths.stream().reduce(Integer::sum).get() == target) {
                count++;
            }
            return;
        }
        for (int i = start; i < nums.length; i++) {
            paths.add(nums[i]);
            backtracking(nums, target, i + 1, paths);
            paths.remove(paths.size() - 1);
            paths.add(-nums[i]);
            backtracking(nums, target, i + 1, paths);
            paths.remove(paths.size() - 1);
        }
    }
}

474.一和零

讲解链接:代码随想录-474.一和零

  1. 确定 dp 数组以及下标的含义:dp[i][j]:最多有 i 个 0 和 j 个 1 的 strs 的最大子集的大小为 dp[i][j]。
  2. 确定递推公式:dp[i][j] 可以由前一个 strs 里的字符串推导出来,strs 里的字符串有 zeroNum 个 0,oneNum 个 1。dp[i][j] 就可以是 dp[i - zeroNum][j - oneNum] + 1。然后我们在遍历的过程中,取 dp[i][j] 的最大值。
    所以递推公式:dp[i][j] = max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1);
public int findMaxForm(String[] strs, int m, int n) {
    int[][] dp = new int[m + 1][n + 1];
    for (String str : strs) {
        int zeroNum = 0;
        int oneNum = 0;
        for (char c : str.toCharArray()) {
            if (c == '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];
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值