Day45~1049. 最后一块石头的重量 II、494. 目标和、474.一和零

118 阅读5分钟

摘要

本文主要介绍了LeetCode动态规划的几个题目,包括1049. 最后一块石头的重量 II、494. 目标和、474.一和零,并在最后对01背包问题进行了总结。

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

1.1 思路

  • 同416.分割等和子集

1、如果套用01背包问题到本题中?

这里,我们可以将总重量的一半看作是背包的容量 m,每个石头的重量看作是物品的重量 weight[i],然后我们可以尝试在背包容量为 m 的情况下选择一些石头,使得它们的总重量尽可能接近 m。这就是一个 0/1 背包问题,其中目标是尽可能填满背包容量 m

1.2 代码

    public int lastStoneWeightII(int[] stones) {
        int sum = getSum(stones);
        int target = sum / 2;
​
        int[] dp = new int[target+1];
        for(int j=target; j>=1; j--) {
            if(j < stones[0]) {
                break;
            }
            dp[j] = stones[0];
        }
        
        for(int i=1; i<stones.length; i++) {
            for(int j=target; j>=1; j--) {
                if(j < stones[i]) {
                    dp[j] = dp[j];
                } else {
                    dp[j] = Math.max(dp[j], dp[j-stones[i]] + stones[i]);
                }
            }
        }
        return Math.abs(sum - dp[target] * 2);
    }
​
    public int getSum(int[] stones) {
        int sum = 0;
        for(int stone : stones) {
            sum += stone;
        }
        return sum;
    }

2、494. 目标和

2.1 思路

1、如果套用01背包问题到本题中?

  • 既然为target,那么就一定有 left组合 - right组合 = target。
  • left + right = sum,而sum是固定的。right = sum - left
  • 公式来了, left - (sum - left) = target 推导出 left = (target + sum)/2 。
  • target是固定的,sum是固定的,left就可以求出来。
  • 此时问题就是在集合nums中找出和为left的组合。

2、如何推导出 dp[j] += dp[j - nums[i]]

  • 只要搞到 nums[i],凑成 dp[j] 就有 dp[j - nums[i]] 种方法

  • 例如:dp[j],j 为5

    • 已经有一个1( nums[i]) 的话,有 dp[4] 种方法 凑成 容量为5的背包。
    • 已经有一个2( nums[i]) 的话,有 dp[3] 种方法 凑成 容量为5的背包。
    • 已经有一个3( nums[i]) 的话,有 dp[2]中方法 凑成 容量为5的背包
    • 已经有一个4( nums[i]) 的话,有 dp[1]中方法 凑成 容量为5的背包
    • 已经有一个5 ( nums[i])的话,有 dp[0]中方法 凑成 容量为5的背包

3、dp[0] 为什么等于 1?

如果数组[0] ,target = 0,那么 bagSize = (target + sum) / 2 = 0。 dp[0]也应该是1, 也就是说给数组里的元素 0 前面无论放加法还是减法,都是 1 种方法。

所以本题我们应该初始化 dp[0] 为 1。

  • 动规五部曲

    • dp 数组以及下标的含义:填满j(包括j)这么大容积的包,有 dp[j] 种方法
    • 递推公式:dp[j] += dp[j - nums[i]]
    • dp 数组如何初始化:dp[0] 为 1
    • dp 数组遍历顺序:一维数组,先遍历物品,然后遍历背包,并且背包倒序遍历
    • 打印 dp 数组

输入:nums: [1, 1, 1, 1, 1], S: 3

bagSize = (S + sum) / 2 = (3 + 5) / 2 = 4

2.2 代码

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

3、474.一和零 *

3.1 思路

这个解法使用一个二维数组 dp,其中 dp[i][j] 表示在允许最多 i 个 0 和 j 个 1 的情况下,能够组成的最大字符串数量。我们遍历每个字符串,统计它包含的 0 和 1 的数量,然后通过动态规划更新 dp 数组。

在更新 dp 数组时,我们从右下角开始,考虑每个可能的情况:选择当前字符串或不选择当前字符串。如果选择当前字符串,则可以从 dp[i - zeros][j - ones] 中得到一个额外的字符串数量,然后更新 dp[i][j] 为当前最大值。最终,dp[m][n] 中存储的就是能够组成的最大字符串数量。

动规五部曲

  • dp 数组以及下标的含义

    • dp[i][j]:最多有i个0和j个1的strs的最大子集的大小为dp[i][j]
  • 递推公式

    • 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);
  • dp 数组如何初始化

    • 01背包的dp数组初始化为0就可以。
  • dp 数组遍历顺序

  • 举例推导dp数组

3.2 代码

    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;
            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];
    }

4、01背包总结

参考资料

代码随想录-1049. 最后一块石头的重量 II

代码随想录-494. 目标和

代码随想录-474.一和零