leetcode-分割等和子集

267 阅读1分钟

「这是我参与2022首次更文挑战的第18天,活动详情查看:2022首次更文挑战」。

题目

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

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

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

思路

要使得两个子集的元素和相等,即任何一个集合的和都是总和的一半。由于题目明确了只包含正整数,所以如果总和sum是奇数,肯定无法分割。如果是偶数,题目就转换成了01背包的变种,就是每个数字num只能用0次或者1次,求是否能把大小为sum/2的背包刚好装满。
我们定义一个二维数组dp,dp[i][j]代表从num[0] ~ num[i]挑选数字,能否刚好组成和为j。对于dp[i][j],我们可以分成以下2种情况:

  • j < num[i],这时num[i]肯定不能选,所以显然有dp[i][j] = dp[i-1][j]
  • j >= num[i],这时num[i]可以选也可以不选,如果选择num[i],dp[i][j] = dp[i-1][j-num[i]];如果不选num[i],dp = [i-1][j],综合起来,就是 dp[i][j] = dp[i-1][j-num[i]] || dp[i-1][j] 所以,我们的状态转移方程就是:
dp[i][j] = dp[i-1][j-num[i]] || dp[i-1][j], j >= num[i]
dp[i][j] = dp[i-1][j], j < num[i]

观察状态转移方程,dp[i][j]只跟dp[i-1][x]有关,所以这里可以进行空间优化,只要定义一个一维数组dp[sum/2+1],每次滚动覆盖即可。当然这里要注意,内层循环的时候,j的值要从target递减来遍历,保证此时的dp[j-num[i]]还是i-1状态的dp[j-num[i]],而不是i状态下的更新后的值。

Java版本代码

class Solution {
    public boolean canPartition(int[] nums) {
        int len = nums.length;
        if (len < 2) {
            return false;
        }
        int sum = 0;
        for (int num : nums) {
            sum += num;
        }
        if (sum % 2 == 1) {
            return false;
        }
        int target = sum >> 1;
        boolean[] dp = new boolean[target + 1];
        dp[0] = true;
        for (int num : nums) {
            for (int j = target; j >= num; j--) {
                dp[j] = dp[j]||dp[j-num];
            }
        }
        return dp[target];
    }
}