给定一个正整数数组,问能否把其分成两个子数组,使这两个子数组的和相等。
分析:先把数组和sum求出来,题目实际上是求是否存在子数组,使该子数组的和等于sum的一半。
方法一:用sum的一半,不断减去数组中元素,把所有可能的差值算出来,看是否有等于0。该方法时间复杂度和空间复杂度较高。
方法二:递增法,分别算出子数组长度分别为1,2 ... n 时,看子数组的和是否有等于sum的一半的情况。
方法三:作为0/1背包问题求解。用 dp[i][j] 表示前 i 个元素能否得到和 j。如果用数组的前 i 个元素能得到和为 j , 则 dp[i][j] 为true,否则为false。
初始条件:dp[i][0] = true,即任意 i 都可以得到和为0的情况;且 dp[0][j] = false(j = 1...n ),因为元素都为正数。
转换过程:对于元素 i ,如果不加该元素,则dp[i][j] = dp[i - 1][j],即如果前i-1个元素能得到j,那么前i个元素也可以;如果加上该元素,则 dp[i][j] = dp[i-1][j-nums[i]],即如果前 i-1 个元素可以得到 j-nums[i],那么加上 nums[i] 就可以得到 j。
public boolean canPartition(int[] nums) { int sum = 0; for (int num : nums) { sum += num; } if ((sum & 1) == 1) { return false; } sum /= 2; int n = nums.length; boolean[][] dp = new boolean[n+1][sum+1]; for (int i = 0; i < dp.length; i++) { Arrays.fill(dp[i], false); } dp[0][0] = true; for (int i = 1; i < n+1; i++) { dp[i][0] = true; } for (int j = 1; j < sum+1; j++) { dp[0][j] = false; } for (int i = 1; i < n+1; i++) { for (int j = 1; j < sum+1; j++) { dp[i][j] = dp[i-1][j]; if (j >= nums[i-1]) { dp[i][j] = (dp[i][j] || dp[i-1][j-nums[i-1]]); } } } return dp[n][sum]; }
还可以进一步对空间进行优化:
public boolean canPartition(int[] nums) { int sum = 0; for (int num : nums) { sum += num; } if ((sum & 1) == 1) { return false; } sum /= 2; int n = nums.length; boolean[] dp = new boolean[sum+1]; Arrays.fill(dp, false); dp[0] = true; for (int num : nums) { for (int i = sum; i > 0; i--) { if (i >= num) { dp[i] = dp[i] || dp[i-num]; } } } return dp[sum]; }