How to divide a set into two subsets such that difference between the sum of numbers in two sets is minimal?

Viewed 91252

Given a set of numbers, divide the numbers into two subsets such that difference between the sum of numbers in two subsets is minimal.

This is the idea that I have, but I am not sure if this is a correct solution:

  1. Sort the array
  2. Take the first 2 elements. Consider them as 2 sets (each having 1 element)
  3. Take the next element from the array.
  4. Decide in which set should this element go (by computing the sum => it should be minimum)
  5. Repeat

Is this the correct solution? Can we do better?

19 Answers

No, Your algorithm is wrong. Your algo follows a greedy approach. I implemented your approach and it failed over this test case: (You may try here)

A greedy algorithm:

#include<bits/stdc++.h>
#define rep(i,_n) for(int i=0;i<_n;i++)
using namespace std;

#define MXN 55
int a[MXN];

int main() {
    //code
    int t,n,c;
    cin>>t;
    while(t--){
        cin>>n;
        rep(i,n) cin>>a[i];
        sort(a, a+n);
        reverse(a, a+n);
        ll sum1 = 0, sum2 = 0;
        rep(i,n){
            cout<<a[i]<<endl;
            if(sum1<=sum2) 
                sum1 += a[i]; 
            else 
                sum2 += a[i]; 
        }
        cout<<abs(sum1-sum2)<<endl;
    }
    return 0;
}

Test case:

1
8 
16 14 13 13 12 10 9 3

Wrong Ans: 6
16 13 10 9
14 13 12 3

Correct Ans: 0
16 13 13 3
14 12 10 9

The reason greedy algorithm fails is that it does not consider cases when taking a larger element in current larger sum set and later a much smaller in the larger sum set may result much better results. It always try to minimize current difference without exploring or knowing further possibilities, while in a correct solution you might include an element in a larger set and include a much smaller element later to compensate this difference, same as in above test case.

Correct Solution:

To understand the solution, you will need to understand all below problems in order:

My Code (Same logic as this):

#include<bits/stdc++.h>
#define rep(i,_n) for(int i=0;i<_n;i++)
using namespace std;

#define MXN 55
int arr[MXN];
int dp[MXN][MXN*MXN];

int main() {
    //code
    int t,N,c;
    cin>>t;
    while(t--){
        rep(i,MXN) fill(dp[i], dp[i]+MXN*MXN, 0);

        cin>>N;
        rep(i,N) cin>>arr[i];
        int sum = accumulate(arr, arr+N, 0);
        dp[0][0] = 1;
        for(int i=1; i<=N; i++)
            for(int j=sum; j>=0; j--)
                dp[i][j] |= (dp[i-1][j] | (j>=arr[i-1] ? dp[i-1][j-arr[i-1]] : 0));

        int res = sum;

        for(int i=0; i<=sum/2; i++)
            if(dp[N][i]) res = min(res, abs(i - (sum-i)));

        cout<<res<<endl;
    }
    return 0;
}

The recursive approach is to generate all possible sums from all the values of array and to check which solution is the most optimal one. To generate sums we either include the i’th item in set 1 or don’t include, i.e., include in set 2.

The time complexity is O(n*sum) for both time and space.T

public class MinimumSubsetSum {

  static int dp[][];
  public static int minDiffSubsets(int arr[], int i, int calculatedSum, int totalSum) {

    if(dp[i][calculatedSum] != -1) return dp[i][calculatedSum];

    /**
     * If i=0, then the sum of one subset has been calculated as we have reached the last
     * element. The sum of another subset is totalSum - calculated sum. We need to return the
     * difference between them.
     */
    if(i == 0) {
      return Math.abs((totalSum - calculatedSum) - calculatedSum);
    }

    //Including the ith element
    int iElementIncluded = minDiffSubsets(arr, i-1, arr[i-1] + calculatedSum,
        totalSum);

    //Excluding the ith element
    int iElementExcluded = minDiffSubsets(arr, i-1, calculatedSum, totalSum);

    int res = Math.min(iElementIncluded, iElementExcluded);
    dp[i][calculatedSum] = res;
    return res;
  }

  public static void util(int arr[]) {
    int totalSum = 0;
    int n = arr.length;
    for(Integer e : arr) totalSum += e;
    dp = new int[n+1][totalSum+1];
    for(int i=0; i <= n; i++)
      for(int j=0; j <= totalSum; j++)
        dp[i][j] = -1;

    int res = minDiffSubsets(arr, n, 0, totalSum);
    System.out.println("The min difference between two subset is " + res);
  }


  public static void main(String[] args) {
    util(new int[]{3, 1, 4, 2, 2, 1});
  }

}

We can use Dynamic Programming (similar to the way we find if a set can be partitioned into two equal sum subsets). Then we find the max possible sum, which will be our first partition. Second partition will be the difference of the total sum and firstSum. Answer will be the difference of the first and second partitions.

public int minDiffernce(int set[]) {      
    int sum = 0;
    int n = set.length;
    for(int i=0; i<n; i++)
        sum+=set[i];

    //finding half of total sum, because min difference can be at max 0, if one subset reaches half
    int target = sum/2;
    boolean[][] dp = new boolean[n+1][target+1];//2

    for(int i = 0; i<=n; i++)
        dp[i][0] = true;
    for(int i= 1; i<=n; i++){
        for(int j = 1; j<=target;j++){
            if(set[i-1]>j) dp[i][j] = dp[i-1][j];
            else dp[i][j] = dp[i-1][j] || dp[i-1][j-set[i-1]];
        }
    }

    // we now find the max sum possible starting from target
    int firstPart = 0;
    for(int j = target; j>=0; j--){
        if(dp[n][j] == true) {
            firstPart = j; break;
        }
    }

    int secondPart = sum - firstPart;
    return Math.abs(firstPart - secondPart);
}

you can use bits to solve this problem by looping over all the possible combinations using bits: main algorithm:

for(int i = 0; i < 1<<n; i++) {
int s = 0;
for(int j = 0; j < n; j++) {
if(i & 1<<j) s += arr[j];
}
int curr = abs((total-s)-s);
ans = min(ans, curr);
}

use long long for greater inputs.

but here I found a recursive and dynamic programming solution and I used both the approaches to solve the question and both worked for greater inputs perfectly fine. Hope this helps :) link to solution

I'll convert this problem to subset sum problem
let's  take array int[] A = { 10,20,15,5,25,33 };
it should be divided into {25 20 10} and { 33 20 } and answer is 55-53=2

Notations : SUM == sum of whole array
            sum1 == sum of subset1
            sum2 == sum of subset1

step 1: get sum of whole array  SUM=108
step 2:  whichever way we divide our array into two part one thing will remain true
          sum1+ sum2= SUM
step 3: if our intention is to get minimum sum difference then sum1 and sum2 should be near SUM/2 (example sum1=54 and sum2=54 then diff=0 )

steon 4: let's try combinations


        sum1 = 54 AND sum2 = 54   (not possible to divide like this) 
        sum1 = 55 AND sum2 = 53   (possible and our solution, should break here)
        sum1 = 56 AND sum2 = 52  
        sum1 = 57 AND sum2 = 51 .......so on
        pseudo code
        SUM=Array.sum();
        sum1 = SUM/2;
        sum2 = SUM-sum1;
        while(true){
          if(subSetSuMProblem(A,sum1) && subSetSuMProblem(A,sum2){
           print "possible"
           break;
          }
         else{
          sum1++;
          sum2--;
         }
         }

Java code for the same

import java.util.ArrayList;
import java.util.List;

public class MinimumSumSubsetPrint {


public static void main(String[] args) {
    int[] A = {10, 20, 15, 5, 25, 32};
    int sum = 0;
    for (int i = 0; i < A.length; i++) {
        sum += A[i];
    }
    subsetSumDynamic(A, sum);

}

private static boolean subsetSumDynamic(int[] A, int sum) {
    int n = A.length;
    boolean[][] T = new boolean[n + 1][sum + 1];


    // sum2[0][0]=true;

    for (int i = 0; i <= n; i++) {
        T[i][0] = true;
    }

    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= sum; j++) {
            if (A[i - 1] > j) {
                T[i][j] = T[i - 1][j];
            } else {
                T[i][j] = T[i - 1][j] || T[i - 1][j - A[i - 1]];
            }
        }
    }

    int sum1 = sum / 2;
    int sum2 = sum - sum1;
    while (true) {
        if (T[n][sum1] && T[n][sum2]) {
            printSubsets(T, sum1, n, A);
            printSubsets(T, sum2, n, A);
            break;
        } else {
            sum1 = sum1 - 1;
            sum2 = sum - sum1;
            System.out.println(sum1 + ":" + sum2);
        }
    }


    return T[n][sum];
}

private static void printSubsets(boolean[][] T, int sum, int n, int[] A) {
    List<Integer> sumvals = new ArrayList<Integer>();
    int i = n;
    int j = sum;
    while (i > 0 && j > 0) {
        if (T[i][j] == T[i - 1][j]) {
            i--;
        } else {
            sumvals.add(A[i - 1]);

            j = j - A[i - 1];
            i--;

        }
    }


    System.out.println();
    for (int p : sumvals) {
        System.out.print(p + " ");
    }
    System.out.println();
}


}

Here is recursive approach

def helper(arr,sumCal,sumTot,n):
    if n==0:
        return abs(abs(sumCal-sumTot)-sumCal)
    
    return min(helper(arr,sumCal+arr[n-1],sumTot,n-1),helper(arr,sumCal,sumTot,n-1))

def minimum_subset_diff(arr,n):
    sum=0
    for i in range(n):
        sum+=arr[i]
        
    return helper(arr,0,sum,n)

Here is a Top down Dynamic approach to reduce the time complexity

dp=[[-1]*100 for i in range(100)]
def helper_dp(arr,sumCal,sumTot,n):
    if n==0:
        return abs(abs(sumCal-sumTot)-sumCal)
    
    if dp[n][sumTot]!=-1:
        return dp[n][sumTot]
    
    return min(helper_dp(arr,sumCal+arr[n-1],sumTot,n-1),helper_dp(arr,sumCal,sumTot,n-1))

def minimum_subset_diff_dp(arr,n):
    sum=0
    for i in range(n):
        sum+=arr[i]
        
    return helper_dp(arr,0,sum,n)

Related