面试算法101:分割等和子集

题目

给定一个非空的正整数数组,请判断能否将这些数字分成和相等的两部分。例如,如果输入数组为[3,4,1],将这些数字分成[3,1]和[4]两部分,它们的和相等,因此输出true;如果输入数组为[1,2,3,5],则不能将这些数字分成和相等的两部分,因此输出false。

分析

如果能够将数组中的数字分成和相等的两部分,那么数组中所有数字的和(记为sum)应该是一个偶数。也可以换一个角度来描述这个问题:能否从数组中选出若干数字,使它们的和等于sum/2(将sum/2记为t)。如果将数组中的每个数字看成物品的重量,也可以这样描述这个问题:能否选择若干物品,使它们刚好放满一个容量为t的背包?由于每个物品(数字)最多只能选择一次,因此这是一个0-1背包问题。(本题一共两个背包)
用函数f(i,j)表示能否从前i个物品(物品标号分别为0,1,…,i-1)中选择若干物品放满容量为j的背包。如果总共有n个物品,背包的容量为t,那么f(n,t)就是问题的解。
当判断能否从前i个物品中选择若干物品放满容量为j的背包时,对标号为i-1的物品有两个选择。一个选择是将标号为i-1的物品放入背包中,如果能从前i-1个物品(物品标号分别为0,1,…,i-2)中选择若干物品放满容量为j-nums[i-1]的背包(即f(i-1,j-nums[i-1])为true),那么f(i,j)就为true。另一个选择是不将标号为i-1的物品放入背包中,如果从前i-1个物品中选择若干物品放满容量为j的背包(即f(i-1,j)为true),那么f(i,j)也为true。

解:递归

public class Test {
    public static void main(String[] args) {
        int[] nums = {1, 2, 3, 5};
        boolean result = canPartition(nums);
        System.out.println(result);
    }

	// 本题一共两个背包
    public static boolean canPartition(int[] nums) {
        int sum = 0;
        for (int num : nums) {
            sum += num;
        }

        if (sum % 2 == 1) {
            return false;
        }

        // 其中一些数字之和为sum/2,则能将所有数字分成和相等的两部分
        // 则问题转化:能否从nums中选出一些数字,使这些数字之和为sum/2
        return subsetSum(nums, sum / 2);
    }

    private static boolean subsetSum(int[] nums, int target) {
        Boolean[][] dp = new Boolean[nums.length + 1][target + 1];
        return helper(nums, dp, nums.length, target);
    }

    private static boolean helper(int[] nums, Boolean[][] dp, int i, int j) {
        if (dp[i][j] == null) {
            if (j == 0) {
                dp[i][j] = true;
            }
            else if (i == 0) {
                dp[i][j] = false;
            }
            else {
                dp[i][j] = helper(nums, dp, i - 1, j);// 不选择元素nums[i-1];
                if (!dp[i][j] && j >= nums[i - 1]) {
                    dp[i][j] = helper(nums, dp, i - 1, j - nums[i - 1]);// 选择元素nums[i-1];
                }
            }
        }

        return dp[i][j];
    }
}

解:迭代

public class Test {
    public static void main(String[] args) {
        int[] nums = {1, 2, 3, 5};
        boolean result = canPartition(nums);
        System.out.println(result);
    }

    public static boolean canPartition(int[] nums) {
        int sum = 0;
        for (int num : nums) {
            sum += num;
        }

        if (sum % 2 == 1) {
            return false;
        }

        // 其中一些数字之和为sum/2,则能将所有数字分成和相等的两部分
        // 则问题转化:能否从nums中选出一些数字,使这些数字之和为sum/2
        return subsetSum(nums, sum / 2);
    }

    private static boolean subsetSum(int[] nums, int target) {
        boolean[][] dp = new boolean[nums.length + 1][target + 1];
        for (int i = 0; i < nums.length; i++) {
            dp[i][0] = true;// 和为0,那么一个数字都不选,一定为true
        }

        for (int i = 1; i <= nums.length; i++) {
            for (int j = 1; j <= target; j++) {
                dp[i][j] = dp[i - 1][j];// // 不选择元素nums[i-1];
                if (!dp[i][j] && j >= nums[i - 1]) {
                    dp[i][j] = dp[i - 1][j - nums[i - 1]];// 选择元素nums[i-1];
                }
            }
        }

        return dp[nums.length][target];
    }

}

你可能感兴趣的:(算法,算法,面试,动态规划)