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

93 阅读3分钟

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

题目链接:1049. 最后一块石头的重量 II

思路:本题的目的是将石头尽量的分成重量相同的两堆,求相撞之后剩下的石头最小重量。这样就可以看成01背包问题。与昨天的分割等和子集十分相似。

动态规划五步曲:

  1. dp[j] 表示容量为j的背包,最多可以背容量为dp[j]的石头。
  2. 递推公式:dp[j] = max(dp[j], dp[j - stones[i]] + stones[i])
  3. 初始化,01背包分割子集问题,dp数组的长度为物品数量的一半。dp[0] = 0,其他非0下标处也为0。
  4. 遍历顺序,先遍历物品,再从后到前遍历背包(防止重复放入)
  5. 举例说明
class Solution {
    public int lastStoneWeightII(int[] stones) {
        // dp[j] 表示容量为j的背包,最多能否放下价值为dp[j]的石头
        int sum = 0;
        for (int stone : stones) {
            sum += stone;
        }
        int len = sum / 2;
        int[] dp = new int[len + 1];
        for (int i = 0; i < stones.length; i++) {
            for (int j = len; j >= stones[i]; j--) {
                dp[j] = Math.max(dp[j], dp[j - stones[i]] + stones[i]);
            }
        }
        return sum - 2 * dp[len];
    }
}

494. 目标和

题目链接:494. 目标和

思路:假设加法的总和为x,那么减法的总和就是sum - x。而我们要求的是x - (sum - x) = target,即x = (target + sum) / 2,此时class Solution { public int findTargetSumWays(int[] nums, int target) { // dp[j] 表示填满容量为j的背包,共有dp[j]种方法 int sum = 0; for (int num : nums) { sum += num; } if ((target + sum) % 2 != 0) return 0; if (Math.abs(target) > sum) return 0; // 背包的容量为nums里面需要相加的总值 int[] dp = new int[(target + sum) / 2 + 1]; dp[0] = 1; for (int i = 0; i < nums.length; i++) { for (int j = dp.length - 1; j >= nums[i]; j--) { dp[j] += dp[j - nums[i]]; } } return dp[dp.length - 1]; } },问题就转化为装满容量为x的背包有多少种方式。本题中我们还要注意两种情况,如果target+sum不能够背2整数,题目无解,如果target的绝对值大于sum,题目无解。

本题就变成了01背包求组合问题,动态规划五步曲:

  1. dp[j] 表示装满容量为j的背包,有dp[j]种方法。
  2. 所有求组合类问题的递推公式都是类似于这种
dp[j] += dp[j - nums[i]]
  1. 初始化dp[0] = 1
  2. 先遍历物品再倒序遍历背包容量
  3. 举例说明
class Solution {
    public int findTargetSumWays(int[] nums, int target) {
        // dp[j] 表示填满容量为j的背包,共有dp[j]种方法
        int sum = 0;
        for (int num : nums) {
            sum += num;
        }
        if ((target + sum) % 2 != 0) return 0;
        if (Math.abs(target) > sum) return 0;
        // 背包的容量为nums里面需要相加的总值
        int[] dp = new int[(target + sum) / 2 + 1];
        dp[0] = 1;
        for (int i = 0; i < nums.length; i++) {
            for (int j = dp.length - 1; j >= nums[i]; j--) {
                dp[j] += dp[j - nums[i]];
            }
        }
        return dp[dp.length - 1];
    }
}

本题也是典型的回溯法,可以用回溯法进行暴搜,但是好像会超时。

474.一和零

题目链接:474. 一和零

思路:本题中strs 数组里的元素就是物品,每个物品都是一个!

而m 和 n相当于是一个背包,两个维度的背包

本题依然是01背包问题,但是背包有两个维度,就是存放0的个数和存放1的个数。

动态规划五步曲:

  1. dp[i][j] 代表最多由i个0和j个1的最大子集大小为dp[i][j]
  2. 可以由两个方向退出dp[i][j],一个是放入strs[i],一个是不放strs[i],

所以递推公式:dp[i][j] = Math.max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1);

  1. 初始化dp[0][0] = 0
  2. 先遍历物品,然后从后向前遍历背包的容量。
  3. 举例说明
class Solution {
    public int findMaxForm(String[] strs, int m, int n) { // 含有两个维度的01背包问题
        // dp[i][j] 代表最多由i个0和j个1的最大子集大小为dp[i][j]
        int[][] dp = new int[m + 1][n + 1];
        for (String str : strs) {
            int zeroNum = 0, oneNum = 0;
            char[] chs = str.toCharArray();
            for (char ch : chs) {
                if (ch == '0') {
                    zeroNum++;
                } else {
                    oneNum++;
                }
            }
            for (int i = dp.length - 1; i >= zeroNum; i--) {
                for (int j = dp[i].length - 1; j >= oneNum; j--) {
                    dp[i][j] = Math.max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1);
                }
            }
        }
        return dp[dp.length - 1][dp[0].length - 1];
    }
}