给定一组n个整数,将该组分成两个n / 2个大小的子集,每个子集的两个子集之和尽可能小。如果n是偶数,则两个子集的大小必须严格为n / 2,如果n为奇数,则一个子集的大小必须为(n-1)/ 2,其他子集的大小必须为(n + 1)/ 2
例如,假设set为{3,4,5,-3,100,1,89,54,23,20},则set的大小为10.此set的输出应为{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。因此,多项式时间解决方案是不可能的。 但是,我正在思考这个问题:
我猜,以上可能会达到线性时间。
但是,这里给出的解决方案过于复杂:http://www.geeksforgeeks.org/tug-of-war/。我无法理解。因此,我只是想问一下,我的解决方案是否正确?考虑到这是一个NP-Hard问题,我认为它应该做什么?如果没有,有人可以像真正简短的解释一样,链接上的代码究竟是如何工作的?谢谢!
答案 0 :(得分:3)
你的解决方案是错误的。
解决Subset-Sum问题/分区问题的贪婪方法失败了。
这是一个简单的反例:
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
答案 1 :(得分:0)
问题是SubSet Sum问题的一种特殊形式,它决定我们是否可以找到两个具有相等总和的分区。这是一个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对,并放入两个分区,以使总和diff最小。 在已排序的数组中选择对(最大,最小)元素,并将其放入较小的(写和)分区。 然后选择第二大元素,并找到其伙伴以将其放在“其他”分区中,以使第二大元素及其伙伴的总和最小化分区的总和。 上述方法将给出次优的解决方案。 NP中的问题已经解决,因此,我们无法找到最佳解决方案,但可以按以下方式提高近似率。 如果我们有次优的解决方案(即sum diff!= 0),那么我们尝试通过将较大分区中的大元素与较小分区中的小元素交换来改进解决方案,以使交换实际上将总和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};
}