416. Partition Equal Subset Sum

题目链接

416. Partition Equal Subset Sum

题目描述

Given a non-empty array containing only positive integers, find if the array can be partitioned into two subsets such that the sum of elements in both subsets is equal.

Note:
Each of the array element will not exceed 100.
The array size will not exceed 200.

Example 1:

Input: [1, 5, 11, 5]

Output: true

Explanation: The array can be partitioned as [1, 5, 5] and [11].
Example 2:

Input: [1, 2, 3, 5]

Output: false

Explanation: The array cannot be partitioned into equal sum subsets.

解题思路

该题目的意思就是看数组中的元素能不能平均分为两半,因此我们首先对原数组进行求和,如果结果是奇数,可以直接返回false。如果结果是偶数,则问题转换为在数组中是否能找到和为sum/2的子数组(注意,子数组中原本数组中的一个数不能使用两次)。

方法一:回溯法
回溯法是解决子集合问题的一个比较经典的方法,无论最后要求原集合中的数字只能用一次还是能用多次。因此,此问题也可以用该方法进行解答。
主要注意的问题:

  1. 下面的算法如果使用从小到大的顺序对原数组进行排序,则会TLE,改为从大到小之后,AC
  2. 下面的算法在遍历过程中没有跳过重复的元素,因此具有一点的巧合性,也没充分利用数组的排序结果,正规的算法应该是在遍历过程中跳过重复的元素。
class Solution {
	 	public boolean canPartition(int[] nums) {
		 if(nums == null || nums.length == 0 || nums.length == 1) {
			 return false;
		 }
		 int sum = getSum(nums);
		 if((sum & 1) != 0) {
			 return false;
		 }
		 
		 int target = sum >> 1;
		 Integer[] temp = new Integer[nums.length];
		 for (int i = 0; i < temp.length; i++) {
			 temp[i] = nums[i];
		 }
		 Arrays.sort(temp, new Comparator<Integer>() {

			@Override
			public int compare(Integer arg0, Integer arg1) {
				return arg1.compareTo(arg0);
			}
		});
		 return dfs(temp, 0, target, 0);
			
	 }
	 
	 private boolean dfs(Integer[] nums, int begin, int target, int currSum) {
		 if(currSum == target) {
			 return true;
		 }
		 if(currSum > target) {
			 return false;
		 }
		 
		 for(int i = begin; i < nums.length; i++) {
			 if(currSum + nums[i] <= target) { //需要去重
				 if(dfs(nums, i + 1, target, currSum + nums[i])) {
					 return true;
				 }
			 }else {
				 return false;
			 }
		 }
		 return false;
	 }
	 
	 private int getSum(int[] nums) {
		 int sum = 0;
		 for(int i = 0; i < nums.length; i++) {
			 sum += nums[i];
		 }
		 return sum;
	 }
}

方法二:动态规划
思路,我们新建一个二维数组dp[i][j],代表前nums中的前i个元素中是否能找到和为j的子集合。
则有:

  1. 状态转移方程为dp[i][j] = dp[i-1][j] || dp[i-1][j - nums[i]]。该方程比较好理解,dp[i-1][j]表示和为j的所有元素中不包含当前的元素,dp[i-1][j - nums[i]]表示和为j的所有元素中包含当前的元素。
  2. dp数组的第一行元素都为false,因此0个元素不可能和为不为0的数。
  3. dp数组的第一列元素都为true,因此,任何一个集合都包含空集,而空集中的数组和为0

因此,有了下面的代码:

class Solution {
			public boolean canPartition(int[] nums) {
		 if(nums == null || nums.length == 0 || nums.length == 1) {
			 return false;
		 }
		 int sum = getSum(nums);
		 if((sum & 1) != 0) {
			 return false;
		 }
		 
		 int target = sum >> 1;
		 
		 boolean[][] dp = new boolean[nums.length + 1][target + 1];
		 int row = nums.length + 1;
		 int col = target + 1;
		 for(int i = 1; i < row; i++) {
			 dp[i][0] = true;
		 }
		 for(int i = 1; i < col; i++) {
			 dp[0][i] = false;
		 }
		 dp[0][0] = true;
		 
		 for(int i = 1; i < row; i++) {
			 for(int j = 1; j < col; j++) {
				 dp[i][j] |= dp[i-1][j];
				 if(j - nums[i-1] >= 0) {
					 dp[i][j] |= dp[i-1][j - nums[i-1]] ;
				 }
			 }
		 }
		
		 return dp[row -1][col - 1];
			
	 }
	 
	 private int getSum(int[] nums) {
		 int sum = 0;
		 for(int i = 0; i < nums.length; i++) {
			 sum += nums[i];
		 }
		 return sum;
	 }
}

方法三:背包问题
方法二使用的是二维的dp数组,观察一下,其实可以将其优化为行数为2dp数组,因为每次更新都只用到了当前数组和其前一行的数组。背包问题的解法和方法二很相似,很像是由方法一优化而来的,但是我不懂为啥关键代码中的第二重for循环需要倒着来遍历(我试着正着进行遍历,但是提交后WA)。其实下面的代码我不懂= =。

class Solution {
			public boolean canPartition(int[] nums) {
		 if(nums == null || nums.length == 0 || nums.length == 1) {
			 return false;
		 }
		 int sum = getSum(nums);
		 if((sum & 1) != 0) {
			 return false;
		 }
		 
		 int target = sum >> 1;
		 
		 boolean[] dp = new boolean[target + 1]; 
		 dp[0] = true;
		 for(int num : nums) {
			 for(int i = target; i >= 1; i--) {
				 if(i - num >= 0) {
					 if(dp[i - num] == true) {
						 dp[i] = true;
					 }
				 }
			 }
		 }
		 return dp[target];
			
	 }
	 
	 private int getSum(int[] nums) {
		 int sum = 0;
		 for(int i = 0; i < nums.length; i++) {
			 sum += nums[i];
		 }
		 return sum;
	 }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值