LintCode刷题——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.
注意事项:
Each of the array element will not exceed 100.
The array size will not exceed 200.
样例:
two subsets: [1, 5, 5], [11]
Given nums = [1, 2, 3, 9]
, return false;
题目大意:
给定一个含正整数的集合,判断该集合能否被分解为两个集合,使得这两个集合中元素的总和相等。
算法分析:
这道题乍看没什么思路,但是换一种说法就可以很自然的联想到用背包问题的解法来求解。这种说法就是从给定的集合中取出若干个元素,使得他们的总和等于该集合总和的一半aim;
所以集合是给定的物品数量,我们的目的是从这些物品中取出恰好满足aim的物品数放入背包;
1、 首先,一个集合能均分为和相等的两部分,其元素之和必为偶数,否则return false;
2、建立一个二维数组DP[i][j]表示将前i个物品放到大小为j的背包中所能放的最多的物品质量;
3、对于每一个物品i,可以选择将其放入当前背包中,也可以选择不放入当前背包中:
①当放入容量为j的背包中时,需要保证该物品能够放入背包,所以DP[i][j] = DP[i][j-nums[i]] + nums[i];
②当不放入容量为j的背包中时,则DP[i][j] = DP[i-1][j];
4、选择3中两种情况中最大的值最为当前DP[i][j]的结果;
5、重复3、4直至将整个二维数组填完,当DP[n-1][aim]==aim,则返回true,表示当容量为aim时该背包中所能放置的物品最大总值等于aim;否则返回false;
算法正确性:
因为DP[i][j] = max{DP[i][j-nums[i]] + nums[i],DP[i-1][j]}保证了截止到前i个物品,总容量为j时该背包中所放的物品总值一定最大,所以对于i=n-1,j=aim时也成立,所以该算法正确;
代码:(一些细节问题在代码中呈现,如二维矩阵的初始化,如何填表等等)
public boolean canPartition(int[] nums) { // write your code here if(nums.length<2){ return false; } int sum = 0; for(int i=0;i<nums.length;i++){ sum+=nums[i]; } if(sum%2==1){ return false; } sum /= 2; int n = nums.length; int[][] DP = new int[n][sum+1]; for(int i=nums[0];i<=sum;i++){ DP[0][i] = nums[0]; } //始终保持当前放的物品是最重的; for(int i=1;i<n;i++){ for(int j=nums[i];j<=sum;j++){ DP[i][j] = Math.max(DP[i-1][j],DP[i-1][j-nums[i]]+nums[i]); } } if(DP[n-1][sum]==sum){ return true; } else{ return false; } }
解法二(用递归的方法解决,但是对于测试集合过大时会导致超时)
public boolean findSolution(int[] nums, int capacity, int index){ if(capacity==0){ return true; } if(capacity<0||index==0&&capacity>0){ return false; } if(findSolution(nums,capacity-nums[index],index-1)){ return true; } return findSolution(nums,capacity,index-1); } public boolean canPartition(int[] nums) { // write your code here if(nums.length<2){ return false; } int sum = 0; for(int i=0;i<nums.length;i++){ sum+=nums[i]; } if(sum%2==1){ return false; } sum /= 2; int index = nums.length-1; return findSolution(nums,sum,index); }
该算法的思路是差不多的,从第一个元素开始对数组中的元素进行递归,每遇到一个元素可以选择放入背包,也可以选择不放入,直到找到最终的满足条件的解为止。但是无疑这种方式的时间复杂度太高了,最坏的情况会达到指数级,除非可以记录中间结果,否则慎用递归。