[416][中等][动态规划][背包] 分割等和子集
题目描述
给定一个只包含正整数的非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
注意:
- 每个数组中的元素不会超过 100 
- 数组的大小不会超过 200 
示例 1:
输入: [1, 5, 11, 5]
输出: true
解释: 数组可以分割成 [1, 5, 5] 和 [11].示例 2:
输入: [1, 2, 3, 5]
输出: false
解释: 数组不能分割成两个元素和相等的子集.解题思路
如果数组中的数字之和是奇数, 肯定找不到两个子集的元素和相等. 偶数才能划分成两个和相等的子集.
现在问题就转化成了能否找到一个子序列的和为sum(raw_list) // 2. 这种在原列表中找出一个子集, 使得子集的和为一个指定数字的问题, 一般转换为背包问题来解决.
以背包问题的思路考虑, 物品是数组中的每一个数, 背包容量就是原数组之和的一般. 由于数组中每个元素只能用1次, 因此这是一个01背包的问题.
每个元素对应的重量就是自身的数值大小, 价值也可以为自身的数值大小. 问题就变成状态矩阵dp[-1][-1]的值是否等于原数组之和的一般. 如果等于说明可以找到一组元素组成子序列, 其和等于原数组之和的一半.
使用滚动数组的代码如下:
class Solution:
    def canPartition(self, nums: List[int]) -> bool:
        n = len(nums)
        total = sum(nums)
        if total % 2 == 1:
            return False
        target = total // 2
        cache = [0] * (target + 1)
        for i in range(n):
            for j in range(target, nums[i] - 1, -1):
                cache[j] = max(cache[j], cache[j - nums[i]] + nums[i])
        return cache[-1] == target恰好装满
上面还是在按照背包问题的传统思路在走. 背包问题的一种扩展就是能否恰好装满指定容量背包的问题. 这个情况在背包问题中有说明.
本题中, 我们不关心装满背包后的价值, 只关心能否恰好装满. 因此状态矩阵的定义可以修改为: dp[i][j]代表使用前i个物品, 能否恰好装满容量为j的背包. 因此dp中的值为True/False. 对应的状态转移公式也变为:
dp[i][j] = dp[i - 1][j] or dp[i - 1][j - w[i]]即考虑当前物品i装与不装入背包两种情况, 只要有一种情况符合就可以.
初始化全部为False, 除了dp[i][0]要 初始化为True. 因此恰好装入容量为0的背包, 只需要什么都不装就可以了.
使用滚动数组的代码如下:
class Solution:
    def canPartition(self, nums: List[int]) -> bool:
        n = len(nums)
        total = sum(nums)
        if total % 2 == 1:
            return False
        target = total // 2
        cache = [False] * (target + 1)
        cache[0] = True
        for i in range(n):
            for j in range(target, nums[i] - 1, -1):
                cache[j] = cache[j] or cache[j - nums[i]]
        return cache[-1]最后更新于
这有帮助吗?