题目

给你一个 只包含正整数 的 非空 数组 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

解答

  • 动态规划

这个解法使用了动态规划。具体来说,我们首先计算数组中所有数字的总和。如果总和是奇数,那么无法分成等和子集,直接返回 false。否则,我们就要找到一个子集,使得它的和等于总和的一半,也就是说,我们要在数组中选择一些数字,使得它们的和等于 target。

我们使用 dp 数组来记录 target 是否可以被分成等和子集。dp[i] 表示 target 为 i 时的情况,dp[i] = true 表示 target 可以被分成等和子集,否则为 false。

对于每一个数字 num,我们遍历 dp 数组的范围是 [target, num],并且更新状态。具体来说,如果 dp[i - num] 为 true,那么 dp[i] 也为 true,因为我们可以在 dp[i - num] 的基础上加上 num 得到 dp[i]。最后,我们返回 dp[target],判断是否可以分成等和子集。

class Solution {
public:
    bool canPartition(vector<int>& nums) {
        int sum = 0;
        for (int num : nums) {
            sum += num;
        }
        if (sum % 2 != 0) {  // 如果总和是奇数,无法分成等和子集
            return false;
        }
        int target = sum / 2;
        vector<bool> dp(target + 1, false);  // 创建一个长度为target+1的vector
        dp[0] = true;  // target为0的情况是可以满足的
        for (int num : nums) {
            for (int i = target; i >= num; i--) {  // 遍历可选数字的范围是[target, num]
                dp[i] = dp[i] || dp[i - num];  // 状态转移方程
            }
        }
        return dp[target];  // 返回target是否可以被分成等和子集
    }
};

dp[i] = dp[i] || dp[i - num];是一个状态转移方程,表示当前状态 dp[i] 是否可以被满足。dp[i] 初始值为 false,如果 dp[i - num] 为 true,也就是说前面已经找到了一组数字使得它们的和等于 i - num,那么我们只需要在这个基础上再加上 num,就可以得到一组数字使得它们的和等于 i,于是我们可以将 dp[i] 设置为 true。

这里使用了逻辑或运算符(||),如果 dp[i] 本来就是 true,那么 dp[i] 的值不会改变,因为或运算符的两个操作数有一个为 true,结果就是 true。如果 dp[i - num] 为 false,那么 dp[i] 仍然是 false。