【问题标题】:Dividing an array into two subsets of equal sizes having minimum difference in sum of values将数组划分为两个大小相等的子集,它们的值之和差异最小
【发布时间】:2015-07-03 20:14:50
【问题描述】:

给定一组 n 个整数,将集合分成两个子集,每个子​​集大小为 n/2,使两个子集之和的差尽可能小。如果 n 为偶数,则两个子集的大小必须严格为 n/2,如果 n 为奇数,则一个子集的大小必须为 (n-1)/2,另一个子集的大小必须为 (n+1)/2 .

例如,设给定集合为 {3, 4, 5, -3, 100, 1, 89, 54, 23, 20},集合的大小为 10。此集合的输出应为 {4, 100 , 1, 23, 20} 和 {3, 5, -3, 89, 54}。两个输出子集的大小均为 5,并且两个子集中的元素总和相同(148 和 148)。 另一个 n 为奇数的例子。设给定集合为 {23, 45, -34, 12, 0, 98, -99, 4, 189, -1, 4}。输出子集应该是 {45, -34, 12, 98, -1} 和 {23, 0, -99, 4, 189, 4}。两个子集中元素之和分别为120和121。

经过大量搜索,我发现这个问题是 NP-Hard。因此,多项式时间解是不可能的。 但是,我在想这样的事情:

  1. 将第一个子集初始化为第一个元素。
  2. 将第二个子集初始化为第二个元素。
  3. 然后根据哪个子集的大小更小以及哪个子集中缺少总和,我将插入下一个元素。

我猜,以上可能会达到线性时间。

但是,这里给出的解决方案太复杂了:http://www.geeksforgeeks.org/tug-of-war/。我无法理解。因此,我只想问,我的解决方案是否正确?考虑到这是一个 NP-Hard 问题,我认为应该这样做吗?如果没有,有人可以简单地解释一下所附链接上的代码是如何工作的吗?谢谢!

【问题讨论】:

    标签: arrays algorithm


    【解决方案1】:

    你的解决方案是错误的。

    这是解决子集和问题/分区问题的贪婪方法,但失败了。

    这是一个简单的反例:

    arr = [1,2,3]
    

    您的解决方案将分配 A={1},B={2},然后选择将 3 分配给 A,并得到 A={1,3}, B={2} - 这不是最佳的,因为最佳解决方案是 A={1,2}, b={3}

    正确的做法是使用动态编程,遵循递归公式:

    D(x,i) = false    i < 0
    D(0,i) = true
    D(x,i) = D(x,i-1) OR D(x-arr[i],i-1)
    

    这可以通过构建一个遵循自下而上递归的表来有效地使用动态规划来完成。

    表格的大小为(SUM/2 + 1)*(n+1)(其中SUM是所有元素的总和),然后在表格中找到最大值使得D(x,n) = true

    【讨论】:

    • 你的例子是正确的。知道了。但是,尽管您的方法听起来正确,但我仍然无法正确理解。您能否附上更多关于D(x,i) 是什么以及真假代表什么的说明?
    • @JohnLui D(x,i) 表示您可以使用第一个 i 元素中的元素获得 sum x 的子集。这意味着,D(SUM/2,n) 意味着有一个大小为 SUM/2 的子集使用列表中的任何元素。
    • @amit 这如何确保平等分割? n/2 n/2?
    • @CoderBC 为确保均等拆分,您需要在递归公式中添加一个维度,并选择元素的数量。它将*n 乘数添加到复杂性中,使其成为O(SUM*n^2) 而不是O(SUM*n)
    【解决方案2】:

    这个问题是子集和问题的一个特化问题,它决定我们是否可以找到任何两个具有相等和的分区。这是一个 NP 完全问题。

    但是,当我们满足以下两个条件时,所讨论的问题要求 2 个这样的相等分区:

    分区大小最多相差 1 分区中的元素之和最小 当然,我们在这里要求为更广义的 NP 完全问题提供次优解决方案。

    例如,对于A=[1, 2, 3, 4, 5, 6, 7, 8, 9],我们可以有这样两个分区{[1, 3, 2, 7, 9], [5 , 4, 6, 8]} 和 diff = abs(22-23) = 1。

    我们的目标是找到具有最佳近似比的次优解决方案。想法是将数组划分为成对的元素,使总和尽可能均匀地分布在各个分区中。因此,每次我们都会尝试取 2 对并将一对放在一个分区中,将另一对放在另一个分区中。

    对数组进行排序 如果元素数量少于 4,则当我们在数组中有 1 个元素或 2 个元素或 3 个元素时,为每种情况创建相应的分区。 否则,我们将每次取 2 对并放入两个分区中,以使总和差异最小化。 在排序后的数组中选择对(最大,最小)元素并将其放入较小的(wr.to sum)分区。 然后选择第二大元素并找到它的伙伴将它们放在“其他”分区中,以便第二大元素和它的伙伴之和最小化分区的总和差异。 上述方法将给出次优解决方案。 NP 中的问题完成了,所以我们不能有一个最优解,但我们可以提高近似比,如下所示。 如果我们有次优解决方案(即 sum diff != 0),那么我们尝试通过将较大分区中的大元素与较小分区中的小元素交换来改进解决方案,以便交换实际上最小化 sum diff。 上述方法的O(n^2)时间和O(n)空间实现如下——

    //overall O(n^2) time and O(n) space solution using a greedy approach
    
    
    ----------
    
    
    ----------
    
    
    ----------
    
    
    
         public static ArrayList<Integer>[] findEqualPartitionMinSumDif(int A[]){
        //first sort the array - O(nlgn)
        Arrays.sort(A);
        ArrayList<Integer> partition1 = new ArrayList<Integer>();
        ArrayList<Integer> partition2 = new ArrayList<Integer>();
    
        //create index table to manage largest unused and smallest unused items
        //O(n) space and O(nlgn) time to build and query the set
        TreeSet<Integer> unused = new TreeSet<>();
        for(int i = 0; i<A.length; i++){
            unused.add(i);
        }
    
        int i = 0;
        int j = A.length-1;
        int part1Sum = 0;
        int part2Sum = 0;
        int diffSum = 0;
    
        //O(n^2) processing time
        while(unused.size() > 0){
            i = unused.first();
            j = unused.last();
            diffSum = part1Sum-part2Sum;
    
            //in case of size of the array is not multiple of 4 then we need to process last 3(or 2 or 1)
            //element to assign partition. This is special case handling
            if(unused.size() < 4){
                switch(unused.size()){
                    case 1: 
                        //put the 1 remaining item into smaller partition
                        if(diffSum > 0){
                            partition2.add(A[i]);
                            part2Sum += A[i];
                        }
                        else{
                            partition1.add(A[i]);
                            part1Sum += A[i];
                        }
                    break;
                    case 2:
                        //among the remaining 2 put the max in smaller and min in larger bucket
                        int max = Math.max(A[i], A[j]);
                        int min = Math.min(A[i], A[j]);
                        if(diffSum > 0){
                            partition2.add(max);
                            partition1.add(min);
                            part2Sum += max;
                            part1Sum += min;
                        }
                        else{
                            partition1.add(max);
                            partition2.add(min);
                            part1Sum += max;
                            part2Sum += min;
                        }
                    break;
                    case 3:
                        //among the remaining 3 put the two having total value greater then the third one into smaller partition
                        //and the 3rd one to larger bucket 
                        unused.remove(i);
                        unused.remove(j);
                        int middle = unused.first();
    
                        if(diffSum > 0){
                            if(A[i]+A[middle] > A[j]){
                                partition2.add(A[i]);
                                partition2.add(A[middle]);
                                partition1.add(A[j]);
                                part2Sum += A[i]+A[middle];
                                part1Sum += A[j];
                            }
                            else{
                                partition2.add(A[j]);
                                partition1.add(A[i]);
                                partition1.add(A[middle]);
                                part1Sum += A[i]+A[middle];
                                part2Sum += A[j];
                            }
                        }
                        else{
                            if(A[i]+A[middle] > A[j]){
                                partition1.add(A[i]);
                                partition1.add(A[middle]);
                                partition2.add(A[j]);
                                part1Sum += A[i]+A[middle];
                                part2Sum += A[j];
                            }
                            else{
                                partition1.add(A[j]);
                                partition2.add(A[i]);
                                partition2.add(A[middle]);
                                part2Sum += A[i]+A[middle];
                                part1Sum += A[j];
                            }
                        }
                    break;
                    default:
                }
    
                diffSum = part1Sum-part2Sum;
                break;
            }
    
            //first take the largest and the smallest element to create a pair to be inserted into a partition
            //we do this for having a balanced distribute of the numbers in the partitions
            //add pair (i, j) to the smaller partition 
            int pairSum = A[i]+A[j];
            int partition = diffSum > 0 ? 2 : 1;
            if(partition == 1){
                partition1.add(A[i]);
                partition1.add(A[j]);
                part1Sum += pairSum;
            }
            else{
                partition2.add(A[i]);
                partition2.add(A[j]);
                part2Sum += pairSum;
            }
    
            //update diff
            diffSum = part1Sum-part2Sum;
            //we have used pair (i, j)
            unused.remove(i);
            unused.remove(j);
            //move j to next big element to the left
            j = unused.last();
            //now find the buddy for j to be paired with such that sum of them is as close as to pairSum
            //so we will find such buddy A[k], i<=k<j such that value of ((A[j]+A[k])-pairSum) is minimized.
            int buddyIndex = unused.first();
            int minPairSumDiff = Integer.MAX_VALUE;
            for(int k = buddyIndex; k<j; k++){
                if(!unused.contains(k))
                    continue;
    
                int compPairSum = A[j]+A[k];
                int pairSumDiff = Math.abs(pairSum-compPairSum);
    
                if(pairSumDiff < minPairSumDiff){
                    minPairSumDiff = pairSumDiff;
                    buddyIndex = k;
                }
            }
    
            //we now find buddy for j. So we add pair (j,buddyIndex) to the other partition
            if(j != buddyIndex){
                pairSum = A[j]+A[buddyIndex];
                if(partition == 2){
                    partition1.add(A[j]);
                    partition1.add(A[buddyIndex]);
                    part1Sum += pairSum;
                }
                else{
                    partition2.add(A[j]);
                    partition2.add(A[buddyIndex]);
                    part2Sum += pairSum;
                }
    
                //we have used pair (j, buddyIndex)
                unused.remove(j);
                unused.remove(buddyIndex);
            }
        }
    
        //if diffsum is greater than zero then we can further try to optimize by swapping 
        //a larger elements in large partition with an small element in smaller partition
        //O(n^2) operation with O(n) space
        if(diffSum != 0){
            Collections.sort(partition1);
            Collections.sort(partition2);
    
            diffSum = part1Sum-part2Sum;
            ArrayList<Integer> largerPartition = (diffSum > 0) ? partition1 : partition2;
            ArrayList<Integer> smallerPartition = (diffSum > 0) ? partition2 : partition1;
    
            int prevDiff = Math.abs(diffSum);
            int largePartitonSwapCandidate = -1;
            int smallPartitonSwapCandidate = -1;
            //find one of the largest element from large partition and smallest from the smaller partition to swap 
            //such that it overall sum difference in the partitions are minimized
            for(i = 0; i < smallerPartition.size(); i++){
                for(j = largerPartition.size()-1; j>=0; j--){
                    int largerVal = largerPartition.get(j);
                    int smallerVal = smallerPartition.get(i);
    
                    //no point of swapping larger value from smaller partition
                    if(largerVal <= smallerVal){
                        continue;
                    }
    
                    //new difference if we had swapped these elements
                    int diff = Math.abs(prevDiff - 2*Math.abs(largerVal - smallerVal));
                    if(diff == 0){
                        largerPartition.set(j, smallerVal);
                        smallerPartition.set(i, largerVal);
                        return new ArrayList[]{largerPartition, smallerPartition};
                    }
                    //find the pair to swap that minimizes the sum diff
                    else if (diff < prevDiff){
                        prevDiff = diff;
                        largePartitonSwapCandidate = j;
                        smallPartitonSwapCandidate = i;
                    }
                }
            }
    
            //if we indeed found one such a pair then swap it. 
            if(largePartitonSwapCandidate >=0 && smallPartitonSwapCandidate >=0){
                int largerVal = largerPartition.get(largePartitonSwapCandidate);
                int smallerVal = smallerPartition.get(smallPartitonSwapCandidate);
                largerPartition.set(largePartitonSwapCandidate, smallerVal);
                smallerPartition.set(smallPartitonSwapCandidate, largerVal);
                return new ArrayList[]{largerPartition, smallerPartition};
            }
        }
    
        return new ArrayList[]{partition1, partition2};
    }
    
    猜你喜欢
    • 2018-07-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-04-07
    • 2011-05-11
    • 2019-12-18
    • 2014-03-24
    • 1970-01-01
    相关资源
    最近更新 更多