• [LeetCode] 416. Partition Equal Subset Sum 相同子集和分割


    Given a non-empty array containing only positive integers, find if the array can be partitioned into two subsets such that the sum of elements in both subsets is equal.

    Note:
    Both the array size and each of the array element will not exceed 100.

    Example 1:

    Input: [1, 5, 11, 5]
    
    Output: true
    
    Explanation: The array can be partitioned as [1, 5, 5] and [11].
    

    Example 2:

    Input: [1, 2, 3, 5]
    
    Output: false
    
    Explanation: The array cannot be partitioned into equal sum subsets.
    

    这道题给了我们一个数组,问这个数组能不能分成两个非空子集合,使得两个子集合的元素之和相同。那么想,原数组所有数字和一定是偶数,不然根本无法拆成两个和相同的子集合,只需要算出原数组的数字之和,然后除以2,就是 target,那么问题就转换为能不能找到一个非空子集合,使得其数字之和为 target。开始博主想的是遍历所有子集合,算和,但是这种方法无法通过 OJ 的大数据集合。于是乎,动态规划 Dynamic Programming 就是不二之选。定义一个一维的 dp 数组,其中 dp[i] 表示原数组是否可以取出若干个数字,其和为i。那么最后只需要返回 dp[target] 就行了。初始化 dp[0] 为 true,由于题目中限制了所有数字为正数,就不用担心会出现和为0或者负数的情况。关键问题就是要找出状态转移方程了,需要遍历原数组中的数字,对于遍历到的每个数字 nums[i],需要更新 dp 数组,既然最终目标是想知道 dp[target] 的 boolean 值,就要想办法用数组中的数字去凑出 target,因为都是正数,所以只会越加越大,加上 nums[i] 就有可能会组成区间 [nums[i], target] 中的某个值,那么对于这个区间中的任意一个数字j,如果 dp[j - nums[i]] 为 true 的话,说明现在已经可以组成 j-nums[i] 这个数字了,再加上 nums[i],就可以组成数字j了,那么 dp[j] 就一定为 true。如果之前 dp[j] 已经为 true 了,当然还要保持 true,所以还要 ‘或’ 上自身,于是状态转移方程如下:

    dp[j] = dp[j] || dp[j - nums[i]]         (nums[i] <= j <= target)

    有了状态转移方程,就可以写出代码了,这里需要特别注意的是,第二个 for 循环一定要从 target 遍历到 nums[i],而不能反过来,想想为什么呢?因为如果从 nums[i] 遍历到 target 的话,假如 nums[i]=1 的话,那么 [1, target] 中所有的 dp 值都是 true,因为 dp[0] 是 true,dp[1] 会或上 dp[0],为 true,dp[2] 会或上 dp[1],为 true,依此类推,完全使的 dp 数组失效了,参见代码如下:

    解法一:

    class Solution {
    public:
        bool canPartition(vector<int>& nums) {
            int sum = accumulate(nums.begin(), nums.end(), 0), target = sum >> 1;
            if (sum & 1) return false;
            vector<bool> dp(target + 1, false);
            dp[0] = true;
            for (int num : nums) {
                for (int i = target; i >= num; --i) {
                    dp[i] = dp[i] || dp[i - num];
                }
            }
            return dp[target];
        }
    };

    这道题还可以用 bitset 来做,感觉也十分的巧妙,bisets 的大小设为 5001,为啥呢,因为题目中说了数组的长度和每个数字的大小都不会超过 100,那么最大的和为 10000,那么一半就是 5000,前面再加上个0,就是 5001 了。初始化把最低位赋值为1,算出数组之和,然后遍历数字,对于遍历到的数字 num,把 bits 向左平移 num 位,然后再或上原来的 bits,这样所有的可能出现的和位置上都为1。举个例子来说吧,比如对于数组 [2,3] 来说,初始化 bits 为1,然后对于数字2,bits 变为 101,可以看出来 bits[2] 标记为了1,然后遍历到3,bits 变为了 101101,看到 bits[5],bits[3],bits[2] 都分别为1了,正好代表了可能的和 2,3,5,这样遍历完整个数组后,去看 bits[sum >> 1] 是否为1即可,参见代码如下:

    解法二:

    class Solution {
    public:
        bool canPartition(vector<int>& nums) {
            bitset<5001> bits(1);
            int sum = accumulate(nums.begin(), nums.end(), 0);
            for (int num : nums) bits |= bits << num;
            return (sum % 2 == 0) && bits[sum >> 1];
        }
    };

    Github 同步地址:

    https://github.com/grandyang/leetcode/issues/416

    类似题目:

    Partition to K Equal Sum Subset

    参考资料:

    https://leetcode.com/problems/partition-equal-subset-sum/

    https://leetcode.com/problems/partition-equal-subset-sum/discuss/90592/01-knapsack-detailed-explanation

    https://leetcode.com/problems/partition-equal-subset-sum/discuss/90590/Simple-C++-4-line-solution-using-a-bitset

    https://leetcode.com/problems/partition-equal-subset-sum/discuss/90588/Concise-C++-Solution-summary-with-DFS-DP-BIT

    https://leetcode.com/problems/partition-equal-subset-sum/discuss/90627/Java-Solution-similar-to-backpack-problem-Easy-to-understand

    LeetCode All in One 题目讲解汇总(持续更新中...)

  • 相关阅读:
    数据库存储的数据为 unicode格式,在.NET 读取出来并转换为繁体字?
    ASP.net 打包并附加数据库
    xml特殊符號
    母板頁面 弹出提示错误的解决方案
    SQL一句實現上一條 下一條信息
    Serializing Faults using XmlSerializer
    VS2005 編譯WebService 為 CS文件
    Java开发的一些文件结构及位置
    kalilinux 其他配置
    完美解决mysql保存中文出现1366错误
  • 原文地址:https://www.cnblogs.com/grandyang/p/5951422.html
Copyright © 2020-2023  润新知