day42 ● 416. 分割等和子集

89 阅读2分钟

题目描述

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

示例1:

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

示例2:

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

解题思路

这道题是一道典型的背包问题,而且是需要恰好装满的0/1背包问题。我们可以用动态规划的思想来解决。

我们定义一个二维数组dp[i][j],其中i表示前i个元素,j表示当前背包的容量。dp[i][j]表示前i个元素能否凑成容量为j的背包,如果能凑成,值为true,否则为false。

状态转移方程如下:

  • 如果当前元素nums[i-1] > j(当前背包容量不足以装入该物品),则dp[i][j] = dp[i-1][j],即不放入该物品,保持原状态;

  • 如果当前元素nums[i-1] <= j,则有两种情况:

  • 不放入该物品,则dp[i][j] = dp[i-1][j];

  • 放入该物品,则dp[i][j] = dp[i-1][j-nums[i-1]]。

最终,我们只需要返回dp[nums.length][target]即可。

代码实现

二维动态规划:

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

一维动态规划:

class Solution {
    public boolean canPartition(int[] nums) {
        int sum = 0;
        for (int num : nums) {
            sum += num;
        }
        if (sum % 2 != 0) {
            return false;
        }
        int target = sum / 2;
        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];
    }
}

总结

二维动态规划和一维动态规划都能够解决这道题,但是一维动态规划的空间复杂度只有O(target),而二维动态规划的空间复杂度是O(nums.length*target),因此一维动态规划更优秀。

在使用一维动态规划时,需要注意内外循环的顺序。内循环需要从大到小进行,因为我们需要先计算dp[j],再计算dp[j-num],以免重复计算。