这个问题是子集和问题的一个特化问题,它决定我们是否可以找到任何两个具有相等和的分区。这是一个 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};
}