• 09_分割等和子集


    416. 分割等和子集

    题目难易:中等

    给定一个只包含正整数的非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。

    注意: 每个数组中的元素不会超过 100 数组的大小不会超过 200

    示例 1:

    • 输入: [1, 5, 11, 5]
    • 输出: true
    • 解释: 数组可以分割成 [1, 5, 5] 和 [11].

    示例 2:

    • 输入: [1, 2, 3, 5]
    • 输出: false
    • 解释: 数组不能分割成两个元素和相等的子集.

    提示:

    • 1 <= nums.length <= 200
    • 1 <= nums[i] <= 100

    【思路】

    这道题目是要找是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。那么只要找到集合里能够出现 sum / 2 的子集总和,就算是可以分割成两个相同元素和子集了。

    动态规划五部曲:

    1、确定dp数组及其下标的含义

    01背包找那个,dp[j]表示:容量为j的背包,所背的物品价值最大可以为dp[j]。

    本题中每一个元素的数组既是重量,也是价值。

    套到本题,dp[j]表示背包总容量(所能装的总重量)是j,放进物品后,背的最大重量为dp[j]

    那么如果背包容量为target, dp[target]就是装满背包之后的重量,所以当 dp[target] == target 的时候,背包就装满了。

    2、确定递推公式

    01背包的递推公式为:dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);

    本题,相当于背包里放入数值,那么物品i的重量是nums[i],其价值也是nums[i]。

    所以递推公式:dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]);

    3、dp数组如何初始化

    在01背包,一维dp如何初始化,已经讲过,

    从dp[j]的定义来看,首先dp[0]一定是0。

    如果题目给的价值都是正整数那么非0下标都初始化为0就可以了,如果题目给的价值有负数,那么非0下标就要初始化为负无穷。

    这样才能让dp数组在递推的过程中取得最大的价值,而不是被初始值覆盖了

    本题题目中 只包含正整数的非空数组,所以非0下标的元素初始化为0就可以了。

    代码如下:

    //题中说:每个数组中的元素不会超过100,数组的大小不会超过200
    //总和不会大于20000,背包最大只需要其中一般,所以10001大小就可以了
    int[] dp = new dp[10001];
    

    4、确定遍历顺序

    如果使用一维dp数组,先遍历物品,在遍历背包。

    代码如下:

    //开始01背包
    for (int i = 0; i < nums.size(); i++) {
    	for (int j = target; j >= nums[i]; j--) {  //每一个元素一定是不可重复放入,所以从大到小遍历
    	dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]);
    	}
    }
    

    5、举例推导dp数组

    dp[j]的数值一定是小于等于j的。

    如果dp[j] == j 说明,集合中的子集总和正好可以凑成总和j,理解这一点很重要。

    完整代码如下:

    class Solution {
        public boolean canPartition(int[] nums) {
            if(nums == null || nums.length == 0) return false;
            int n = nums.length;
            int sum = 0;
            for(int num : nums) {
                sum += num;
            }
            //总和为奇数,不能平分
            if(sum % 2 != 0) return false;
            int target = sum / 2;
            int[] dp = new int[target + 1];
            for(int i = 0; i < n; i++) {  //先遍历物品
                for(int j = target; j >= nums[i]; j--) {
                    //物品 i 的重量是 nums[i],其价值也是 nums[i]
                    dp[j] = Math.max(dp[j], dp[j - nums[i]] + nums[i]);
                }
               
                //剪枝一下,每一次完成内部的for-loop,立即检查是否dp[target] == target,优化时间复杂度(26ms -> 20ms)
                if(dp[target] == target)
                    return true;
            }
            return dp[target] == target;
        }
    }
    

    总结

    这道题目就是一道01背包应用类的题目,需要我们拆解题目,然后套入01背包的场景。

    01背包相对于本题,主要要理解,题目中物品是nums[i],重量是nums[i],价值也是nums[i],背包体积是sum/2。

    看代码的话,就可以发现,基本就是按照01背包的写法来的。

  • 相关阅读:
    你真的面向对象了吗?
    002-第一代硬件系统架构确立及产品选型
    计算机结构体系:系统CPI计算例题(1.5)
    使用Pytorch测试cuda设备的性能(单卡或多卡并行)
    零基础学韩语-看韩剧追欧巴
    【golang】建议收藏的golang瑞士军刀-9个工具方法
    Vue实现无限滚动加载更多内容(懒加载)或实现查看更多按钮
    label_studio中uwsgi内存占用高的问题排查
    【无标题】
    Mac电脑其他文件占用超过一大半的内存如何清理?
  • 原文地址:https://www.cnblogs.com/codingbao/p/18233994