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);
    }

该算法的思路是差不多的,从第一个元素开始对数组中的元素进行递归,每遇到一个元素可以选择放入背包,也可以选择不放入,直到找到最终的满足条件的解为止。但是无疑这种方式的时间复杂度太高了,最坏的情况会达到指数级,除非可以记录中间结果,否则慎用递归。

 

 

 

posted @ 2017-09-18 21:12  Revenent  阅读(169)  评论(0编辑  收藏  举报