【代码随想录|刷题记录Day43】1049.最后一块石头的重量II、494.目标和、474.一和零

53 阅读3分钟

题目列表

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

  494.目标和

  474.一和零

解题过程

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

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

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

  • 如果 x == y,那么两块石头都会被完全粉碎;
  • 如果 x != y,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x

最后,最多只会剩下一块 石头。返回此石头 最小的可能重量 。如果没有石头剩下,就返回 0

思路: 其实就是将 石头之和/2 ,然后将这个值作为 target ,求解01背包问题,最后 石头之和-2✖️真正装进去的重量 就是所求的 最后一块石头的重量

动态规划五部曲:

  • 确定dp数组以及下标的含义
    • dp[j]表示容量(这里说容量更形象,其实就是重量)为j的背包,最多可以背最大重量为dp[j]。
  • 确定递推公式
    • dp[j] = max(dp[j], dp[j - stones[i]] + stones[i])。
  • dp数组初始化
    • dp[j]都初始化为0。
  • 确定遍历顺序
    • 如果使用一维dp数组,物品遍历的for循环放在外层,遍历背包的for循环放在内层,且内层for循环倒序遍历!
  • 举例推导dp数组
class Solution {
    public int lastStoneWeightII(int[] stones) {
        int sum = 0;
        for (int stone : stones) {
            sum += stone;
        }
        int target = sum >> 1;
        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 - 2 * dp[target];
    }
}

二维dp数组解法:

class Solution {
    public int lastStoneWeightII(int[] stones) {
        int sum = 0;
        for (int s : stones) {
            sum += s;
        }

        int target = sum / 2;
        //初始化,dp[i][j]为可以放0-i物品,背包容量为j的情况下背包中的最大价值
        int[][] dp = new int[stones.length][target + 1];
        //dp[i][0]默认初始化为0
        //dp[0][j]取决于stones[0]
        for (int j = stones[0]; j <= target; j++) {
            dp[0][j] = stones[0];
        }

        for (int i = 1; i < stones.length; i++) {
            for (int j = 1; j <= target; j++) {//注意是等于
                if (j >= stones[i]) {
                    //不放:dp[i - 1][j] 放:dp[i - 1][j - stones[i]] + stones[i]
                    dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - stones[i]] + stones[i]);
                } else {
                    dp[i][j] = dp[i - 1][j];
                }
            }
        }

        System.out.println(dp[stones.length - 1][target]);
        return (sum - dp[stones.length - 1][target]) - dp[stones.length - 1][target];
    }
}

2、494.目标和

给你一个整数数组 nums 和一个整数 target

向数组中的每个整数前添加 '+''-' ,然后串联起所有整数,可以构造一个 表达式

  • 例如,nums = [2, 1] ,可以在 2 之前添加 '+' ,在 1 之前添加 '-' ,然后串联起来得到表达式 "+2-1"

返回可以通过上述方法构造的、运算结果等于 target 的不同 表达式 的数目。

思路: 将整数数组分为两部分,一部分用来计算加法值,另一部分用来计算减法值。加法值部分总和为 x,减法值部分总和为 sum - x,那么 target = x - (sum - x),得 x = (target + sum) / 2,以此为目标值求解01背包问题(方法总数)。

class Solution {
    public int findTargetSumWays(int[] nums, int target) {
        int sum = 0;
        for (int num : nums) {
            sum += num;
        }
        if (target < 0 && sum < - target) {
            return 0;
        }
        if ((target + sum) % 2 != 0) {
            return 0;
        }
        int newTarget = (target + sum) / 2;
        int[] dp = new int[newTarget + 1];
        dp[0] = 1;
        for (int i = 0; i < nums.length; i++) {
            for (int j = newTarget; j >= nums[i]; j--) {
                dp[j] += dp[j - nums[i]];
            }
        }
        return dp[newTarget];
    }
}

3、474.一和零

给你一个二进制字符串数组 strs 和两个整数 mn

请你找出并返回 strs 的最大子集的长度,该子集中 最多m0n1

如果 x 的所有元素也是 y 的元素,集合 x 是集合 y子集

思路: 本题实质上还是01背包问题,只是这个背包有两个维度,一个是m 一个是n,而不同长度的字符串就是不同大小的待装物品。

动态规划五部曲:

  • 确定dp数组以及下标的含义
    • dp[i][j]:最多有i个0和j个1的strs的最大子集的大小为dp[i][j]。
  • 确定递推公式
    • dp[i][j] = max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1)。
  • dp数组初始化
    • 初始化为0。
  • 确定遍历顺序
    • 一样。
  • 举例推导dp数组
class Solution {
    public int findMaxForm(String[] strs, int m, int n) {
        // dp[i][j]表示i个0和j个1时的最大子集
        int[][] dp = new int[m + 1][n + 1];
        int oneNum, zeroNum;
        // 遍历
        for (String str : strs) {
            oneNum = 0;
            zeroNum = 0;
            // 统计当前字符串中的0和1的个数
            for (char ch : str.toCharArray()) {
                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];
    }
}