如何将一组数字分成两组,使其总和的差异最小

时间:2015-07-04 16:47:31

标签: java arrays partition

如何编写Java程序将一组数字分成两组,使得各个数字之和的差异最小。

例如,我有一个包含整数的数组 - [5,4,8,2]。我可以把它分成两个阵列 - [8,2]和[5,4]。假设给定的一组数字,可以像上面的例子一样有一个独特的解决方案,如何编写一个Java程序来实现解决方案。即使我能够找出最小可能的差异,也没关系。 假设我的方法接收一个数组作为参数。该方法必须首先将接收的数组分成两个数组,然后添加其中包含的整数。此后,它必须返回它们之间的差异,以便差异可能最小。

P.S.-我在这里看了看,但找不到任何具体的解决方案。最可能的解决方案似乎在这里给出 - divide an array into two sets with minimal difference。但我无法从该线程中收集如何编写Java程序以获得问题的明确解决方案。

修改

在查看@Alexandru Severin的评论之后,我尝试了一个java程序。它适用于一组数字[1,3,5,9],但不适用于另一组[4,3,5,9,11]。以下是该计划。请建议更改: -

 import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class FindMinimumDifference {
public static void main(String[] args) {
    int[] arr= new int[]{4,3,5,9, 11};  
    FindMinimumDifference obj= new FindMinimumDifference();
    obj.returnMinDiff(arr);
}

private int  returnMinDiff(int[] array){


    int diff=-1;
    Arrays.sort(array);
    List<Integer> list1= new ArrayList<>();
    List<Integer> list2= new ArrayList<>();
    int sumOfList1=0;
    int sumOfList2=0;
    for(int a:array){
        for(Integer i:list1){
            sumOfList1+=i;
        }
        for(Integer i:list2){
            sumOfList2+=i;
        }
        if(sumOfList1<=sumOfList2){
        list1.add(a);
        }else{
            list2.add(a);
        }
    }

    List<Integer> list3=new ArrayList<>(list1);   
    List<Integer> list4= new ArrayList<>(list2);   
    Map<Integer, List<Integer>> mapOfProbables= new HashMap<Integer, List<Integer>>();
    int probableValueCount=0;
    for(int i=0; i<list1.size();i++){  
        for(int j=0; j<list2.size();j++){
            if(abs(list1.get(i)-list2.get(j))<
abs(getSumOfEntries(list1)-getSumOfEntries(list2))){
                List<Integer> list= new ArrayList<>();
                list.add(list1.get(i));
                list.add(list2.get(j));    
                mapOfProbables.put(probableValueCount++, list);
            }
        }
    }
    int minimumDiff=abs(getSumOfEntries(list1)-getSumOfEntries(list2));
    List resultList= new ArrayList<>();
    for(List probableList:mapOfProbables.values()){  
        list3.remove(probableList.get(0));
        list4.remove(probableList.get(1));
        list3.add((Integer)probableList.get(1));
        list4.add((Integer)probableList.get(0));
        if(minimumDiff>abs(getSumOfEntries(list3)-getSumOfEntries(list4))){ 
// valid exchange 
                minimumDiff=abs(getSumOfEntries(list3)-getSumOfEntries(list4));
                resultList=probableList;
        }

    }

    System.out.println(minimumDiff);

    if(resultList.size()>0){
        list1.remove(resultList.get(0));
        list2.remove(resultList.get(1));
        list1.add((Integer)resultList.get(1));
        list2.add((Integer)resultList.get(0));
    }

    System.out.println(list1+""+list2);  // the two resulting set of 
// numbers with modified data giving expected result

    return minimumDiff;
}

private static int getSumOfEntries(List<Integer> list){
    int sum=0;
    for(Integer i:list){
        sum+=i;
    }
    return sum;
}
private static int abs(int i){
    if(i<=0) 
        i=-i;
    return i;
}
}

5 个答案:

答案 0 :(得分:3)

首先,对数组进行排序,然后将第一个成员放入组中,将第二个成员放入另一个成员中,这样就不会起作用了,这就是原因:

给定输入[1,2,3,100]。 结果将是:[1,3][2,100],显然是错误的。 正确答案应为:[1,2,3][100]

你可以在google上找到很多关于这个问题的优化算法,但是因为我认为你是初学者,我会试着给你一个你可以实现的简单算法:

  1. 对数组进行排序
  2. 从最高值到最低值迭代
  3. 对于每次迭代,计算每个组的总和,然后将该元素添加到具有最小总和
  4. 的组中

    在循环结束时,你应该有两个相当平衡的数组。例如:

    Array: [1,5,5,6,7,10,20]
    i1: `[20] []`
    i2: `[20] [10]`
    i3: `[20] [10,7]`
    i4: `[20] [20,7,6]`
    i5: `[20,5] [10,7,6]`
    i6: `[20,5] [10,7,6,5]`
    i7: `[20,5,1] [10,7,6,5]`
    

    总和为2628。如您所见,我们可以进一步优化解决方案,如果我们交换56导致[20,6,1][20,7,5,5]总和相等。

    对于此步骤,您可以:

    1. 查找(x,y)在group1中的所有元素组xy在group2中,|x-y| < |sum(group1) - sum(group2)|
    2. 循环播放所有群组并尝试与x交换y,直至获得最小差异
    3. 每次交换后检查总和最高的组中的最小值是否高于组的差异,如果是,则将其转移到另一组
    4. 此算法将始终返回最佳解决方案,并且比贪婪方法好得多。然而,就复杂性,速度和记忆而言,它并不是最佳的。如果对于非常大的阵列需要它并且资源有限,则最佳算法可能根据速度/存储比率和接受的误差百分比而不同。

答案 1 :(得分:2)

这是分区问题https://en.wikipedia.org/wiki/Partition_problem

的变体

如果您需要最佳解决方案,则必须测试每个可能的输出集合组合。这对于小型集合可能是可行的,但对于大型输入是不可行的。

一个很好的近似是我在下面提出的贪婪算法。

  

当集合中的数字为时,这种启发式在实践中很有效   大小与其基数相同或更小,但事实并非如此   保证产生最佳分区。

首先,您需要将输入放在可排序的集合中,例如List。

1)对输入集合进行排序。

2)创建2个结果集。

3)迭代排序的输入。如果索引甚至被置于result1中,则将该项放在result2中。

  List<Integer> input = new ArrayList<Integer>();
  Collections.sort(input);
  Set<Integer> result1 = new HashSet<Integer>();
  Set<Integer> result2 = new HashSet<Integer>();
  for (int i = 0; i < input.size(); i++) {
      if (i % 2 == 0) {// if i is even
          result1.add(input.get(i));
      } else {
          result2.add(input.get(i));
      }
  }

答案 2 :(得分:1)

我似乎已经为此获得了完美的解决方案。 Java程序下面的工作完美。唯一的假设是,给定的问题有唯一的解决方案(只有一个解决方案)。该假设暗示 - 仅非零数。我把这个程序放在下面。我请求每个人告诉程序是否可能在某些情况下失败,或者是否可以某种方式改进/优化它。 对Alexandru Severin先生的算法的认可是本主题中的答案之一。

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class FindMinimumDifference {

static List<Integer> list1= new ArrayList<>();
static List<Integer> list2= new ArrayList<>();

public static void main(String[] args) {
    int[] arr= new int[]{3,-2,9,7};  
    // tested for these sample data:- [1,5,9,3] ; [4,3,5,9,11] ; 
//[7,5,11,2,13,15,14] ; [3,2,1,7,9,11,13] ; 
    //[3,1,0,5,6,9] ; [6,8,10,2,4,0] ; [3,1,5,7,0] ; [4,-1,5,-3,7] ; [3,-2,9,7]

    System.out.println("the minimum possible difference is: "+returnMinDiff(arr));
    System.out.println("the two resulting set of nos. are: "+list1+" and "+list2);
}

private static int  returnMinDiff(int[] array){
    int diff=-1;
    Arrays.sort(array);

    for(int a:array){
        int sumOfList1=0;
        int sumOfList2=0;

        for(Integer i:list1){
            sumOfList1+=i;
        }
        for(Integer i:list2){
            sumOfList2+=i;
        }
        if(sumOfList1<=sumOfList2){
        list1.add(a);
        }else{
            list2.add(a);
        }
    }

    List<Integer> list3=new ArrayList<>(list1);   
    List<Integer> list4= new ArrayList<>(list2); 
    if(list3.size()!=list4.size()){     // both list should contain equal no. of entries. 
        //If not, add 0 to the list having lesser no. of entries
        if(list3.size()<list4.size()){
            list3.add(0);
        }else{
            list4.add(0);
        }
    }
    Map<Integer, List<Integer>> mapOfProbables= new HashMap<Integer, List<Integer>>();
    int probableValueCount=0;
    for(int i=0; i<list3.size();i++){  
        for(int j=0; j<list4.size();j++){
            if(abs(list3.get(i)-list4.get(j))
   <abs(getSumOfEntries(list3)-getSumOfEntries(list4))){
                List<Integer> list= new ArrayList<>();
                list.add(list3.get(i));
                list.add(list4.get(j));    
                mapOfProbables.put(probableValueCount++, list);
            }
        }
    }
    int minimumDiff=abs(getSumOfEntries(list1)-getSumOfEntries(list2));
    List resultList= new ArrayList<>();
    for(List probableList:mapOfProbables.values()){ 
        list3=new ArrayList<>(list1);   
        list4= new ArrayList<>(list2);
        list3.remove(probableList.get(0));
        list4.remove(probableList.get(1));
        list3.add((Integer)probableList.get(1));
        list4.add((Integer)probableList.get(0));
        if(minimumDiff>abs(getSumOfEntries(list3)-getSumOfEntries(list4))){ // valid exchange 
                minimumDiff=abs(getSumOfEntries(list3)-getSumOfEntries(list4));
                resultList=probableList;
        }

    }

    if(resultList.size()>0){   // forming the two set of nos. whose difference of sum comes out to be minimum
        list1.remove(resultList.get(0));
        list2.remove(resultList.get(1));
        if(!resultList.get(1).equals(0) )   // (resultList.get(1).equals(0) && !list1.contains(0))
            list1.add((Integer)resultList.get(1));
        if(!resultList.get(0).equals(0) || (resultList.get(0).equals(0) && list2.contains(0)))
            list2.add((Integer)resultList.get(0));
    }

    return minimumDiff; // returning the minimum possible difference
}

private static int getSumOfEntries(List<Integer> list){
    int sum=0;
    for(Integer i:list){
        sum+=i;
    }
    return sum;
}
private static int abs(int i){
    if(i<=0) 
        i=-i;
    return i;
}
}

答案 3 :(得分:0)

似乎您对算法比对代码更感兴趣。所以,这是我的伪代码: -

int A[];//This contains your elements in sorted (descending) order
int a1[],a2[];//The two sub-arrays
int sum1=0,sum2=0;//These store the sum of the elements of the 2 subarrays respectively
for(i=0;i<A.length;i++)
{
//Calculate the absolute difference of the sums for each element and then add accordingly and thereafter update the sum
    if(abs(sum1+A[i]-sum2)<=abs(sum2+A[i]-sum1))
           {a1.add(A[i]);
            sum1+=A[i];}
    else
           {a2.add(A[i]);
            sum2+=A[i];}
}

这适用于所有整数,无论​​是积极的还是消极的。

答案 4 :(得分:0)

对于这个问题,假设我们可以将数组分成两个子数组,使它们的总和相等。 (即使认为它们不相同,它也会起作用)

因此,如果数组中的元素总和为S.您的目标是找到一个总和为S / 2的子集。你可以为此编写一个递归函数。

O(N^2)

您也可以为此编写等效的printf动态编程代码。 我只是在展示这个想法。

所以当你发现这个集合的总和为S / 2时,你自动将数组分成两部分,总和相同(这里是S / 2)。