416. Partition Equal Subset Sum

题目:假设一个非空数组中仅包含正整数,判断是否可以将该数组分为两个子数组,使得两个子数组中元素的和相等

题解:这是一个0-1背包问题,假设原数组的和为sum,问题可以转化为:找到一个原数组的子集,使其子集内的元素之和等于sum/2。

方案一:递归算法

假设原数组为nums,其中有n个数,其和为sum,那么需要在这n个数中找出一个子集使其和正好等于newSum(newSum = sum/2)。将此问题记为f(n,newSum),该问题可以分为两种情况,第一种情况不将第n个数放入子集中,那么前n-1个数的问题表示为f(n-1,newSum)。第二种情况考虑将第n个数放入子集中,前n-1个数的问题表示为f(n-1,newSum-nums[n])。进而可以得到递归表达式f(n,newSum)=f(n-1,newSum)||f(n-1,newSum-nums[n])。根据公式编写代码如下。递归算法的时间复杂度为O(2^n)。

	/**
     * 递归算法
     *
     * @param nums
     * @return
     */
    public boolean canPartition(int[] nums) {
        if (nums == null || nums.length == 0) {
            return false;
        }
        int sum = 0;
        for (int num : nums) {
            sum += num;
        }
        if ((sum & 1) == 1) {
            return false;
        }
        return tryPartition(nums, sum / 2, nums.length - 1);
    }

    public boolean tryPartition(int[] nums, int sum, int index) {
        // 当背包容量为0时,说明可以正好填充满
        if (sum == 0) {
            return true;
        }
        // 当背包容量为负数或者没有数可用时,说明不能正好填充满
        if (sum < 0 || index < 0) {
            return false;
        }

        return tryPartition(nums, sum, index - 1) // 不考虑当前数
                || tryPartition(nums, sum - nums[index], index - 1); // 考虑当前数

    }

方案二:动态规划算法

采用自底向上的思想,通过如下两个表格找到规律,编写相应的代码。时间复杂度为O(n*sum),空间复杂度为O(n*sum)。

0 1 2 3
1 5 11 5
0 1 2 3 4 5 6 7 8 9 10 11
0 true true
1 true true true true
2 true true true true true
3 true true true true true true
代码:
	/**
     * 动态规划算法
     *
     * @param nums
     * @return
     */
    public boolean canPartition2(int[] nums) {
        if (nums == null || nums.length == 0) {
            return false;
        }
        int sum = 0;
        for (int num : nums) {
            sum += num;
        }
        if ((sum & 1) == 1) {
            return false;
        }
        int newSum = sum / 2;
        boolean[][] dp = new boolean[nums.length][newSum + 1];
        dp[0][0] = true;
        if (nums[0] <= newSum) {
            dp[0][nums[0]] = true;
        }
        for (int i = 1; i < nums.length; i++) {
            for (int j = 0; j <= newSum; j++) {
                dp[i][j] = dp[i - 1][j];
                if (j >= nums[i]) {
                    dp[i][j] = dp[i][j] || dp[i - 1][j - nums[i]];
                }
            }
        }
        return dp[nums.length - 1][newSum];
    }
改进后的动态规划算法代码:

改进后的代码时间复杂度为O(n*sum),空间复杂度为O(sum)。

	/**
     * 优化后的动态规划算法
     *
     * @param nums
     * @return
     */
    public boolean canPartition(int[] nums) {
        if (nums == null || nums.length == 0) {
            return false;
        }
        int sum = 0;
        for (int num : nums) {
            sum += num;
        }
        if ((sum & 1) == 1) {
            return false;
        }
        int newSum = sum / 2;
        boolean[] dp = new boolean[newSum + 1];
        dp[0] = true;
        if (nums[0] <= newSum) {
            dp[nums[0]] = true;
        }
        for (int i = 1; i < nums.length; i++) {
            for (int j = newSum; j >= nums[i]; j--) {
                dp[j] = dp[j] || dp[j - nums[i]];
            }
        }
        return dp[newSum];
    }

你可能感兴趣的:(LeetCode,Java)