leetcode
leetcode 351 ~ 400
分割等和子集

分割等和子集

难度:

标签:

题目描述

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

 

示例 1:

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

示例 2:

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

 

提示:

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

代码结果

运行时间: 30 ms, 内存: 16.0 MB


// Java Stream Solution
// 思路:
// 1. 使用Stream计算总和。
// 2. 如果总和是奇数,则返回false。
// 3. 以动态编程的方式计算是否可以分成和相等的两个子集。
 
import java.util.Arrays;
import java.util.stream.IntStream;
 
public class StreamSolution {
    public boolean canPartition(int[] nums) {
        int sum = IntStream.of(nums).sum();
        if (sum % 2 != 0) return false;
        int target = sum / 2;
        boolean[] dp = new boolean[target + 1];
        dp[0] = true;
        Arrays.stream(nums).forEach(num -> {
            for (int j = target; j >= num; j--) {
                dp[j] = dp[j] || dp[j - num];
            }
        });
        return dp[target];
    }
}

解释

方法:

这个题解使用了动态规划的思路。首先判断数组的总和是否为奇数,如果是则不可能平分成两个等和子集。然后将目标和设为总和的一半。接下来使用一个整数dp作为状态,其二进制表示中从右往左第i位表示是否存在一个和为i的子集。通过遍历数组中的每个数x,将dp中所有已经置为1的位向右移动x位,再与原dp取或,这样就在dp中记录了当前数组前缀中所有可能的子集和。如果右移后dp的最低位变为1,说明存在一个子集的和等于目标和,直接返回True。遍历结束后,如果dp最低位仍为0,说明不存在符合条件的子集划分,返回False。

时间复杂度:

O(n * target)

空间复杂度:

O(1)

代码细节讲解

🦆
在题解中,为什么总和为奇数时就可以直接判断无法平分成两个等和子集?
如果一个数组的总和是奇数,那么它不可能被划分为两个和相等的子集。因为两个相等的数相加的结果总是偶数,而奇数永远无法被2整除得到一个整数。所以,如果数组的总和是奇数,我们可以直接得出结论,不可能将其平分为两个等和的子集。
🦆
请问在进行动态规划时,为什么使用位运算而不是传统的动态规划表来记录子集和的存在性?
在这个问题中,使用位运算而不是传统的动态规划表可以大大减少空间复杂度。位运算通过利用整数的每一位来代表子集和的存在与否,可以使用更少的空间(仅需一个整数变量)存储状态信息。此外,位运算提供了非常高效的方式来更新状态,例如通过位移和位或操作来合并状态。这种方法在处理子集和问题时非常高效且节省空间。
🦆
题解中提到的`dp |= dp >> x`操作具体是如何更新dp的状态的?请解释这一步中位运算的逻辑。
这一位运算操作是动态规划解法中的核心。`dp |= dp >> x`操作的含义是:首先将dp右移x位,这代表将所有已经存在的子集和都加上新元素x的值,然后使用位或(|)操作将这个结果合并回dp。这样,如果之前存在某个和为i的子集,通过这个操作,就能得到一个新的和为i + x的子集。位或操作确保了dp在这些新的子集和位置上被设置为1,从而在不丢失任何已有信息的情况下更新了可能的子集和。
🦆
为什么题解中判断`if dp & 1`能够确定是否存在一个子集的和等于目标和?
在这个题解中,dp的二进制表示中的每一位i表示是否存在一个子集的和为i。特别地,二进制的最低位(即第0位)表示和为0的情况。由于目标是判断是否存在一个子集的和等于数组总和的一半(target),这个值被设置在dp中的最高位。因此,判断最低位是否为1(即`if dp & 1`)实际上是在检查是否存在一个子集,其和恰好等于target。这是因为在初始化时,dp被设置为指向target的位,随后的操作可能会将这个位向右移动直到第0位,如果这个位为1,则表示存在这样的子集。

相关问题

划分为k个相等的子集

给定一个整数数组  nums 和一个正整数 k,找出是否有可能把这个数组分成 k 个非空子集,其总和都相等。

 

示例 1:

输入: nums = [4, 3, 2, 3, 5, 2, 1], k = 4
输出: True
说明: 有可能将其分成 4 个子集(5),(1,4),(2,3),(2,3)等于总和。

示例 2:

输入: nums = [1,2,3,4], k = 3
输出: false

 

提示:

  • 1 <= k <= len(nums) <= 16
  • 0 < nums[i] < 10000
  • 每个元素的频率在 [1,4] 范围内