Как разделить множество на два подмножества так, чтобы разница между суммой чисел в двух наборах была минимальной? - PullRequest
47 голосов
/ 06 июля 2011

Учитывая набор чисел, разделите числа на два подмножества так, чтобы разница между суммой чисел в двух подмножествах была минимальной.

Это идея, которая у меня есть, ноЯ не уверен, что это правильное решение:

  1. Сортировать массив
  2. Взять первые 2 элемента.Рассмотрим их как 2 набора (каждый из которых имеет 1 элемент)
  3. Возьмите следующий элемент из массива.
  4. Решите, в каком наборе должен идти этот элемент (вычисляя сумму => она должна быть минимальной)
  5. Повтор

Это правильное решение?Можем ли мы сделать лучше?

Ответы [ 15 ]

35 голосов
/ 06 июля 2011

Вариант решения , который вы описываете, является проблемой NP-complete и называется проблемой раздела . Существует ряд приближений , которые во многих случаях обеспечивают оптимальные или, по крайней мере, достаточно хорошие решения.

Простой алгоритм, который вы описали, - это способ, которым дети выбирают команды. Этот жадный алгоритм работает замечательно, если числа в наборе имеют одинаковые порядки величины.

Статья Самая сложная задача , автором American Scientist , дает превосходный анализ проблемы. Вы должны прочитать и прочитать!

7 голосов
/ 06 июля 2011

Нет, это не работает. Не существует решения за полиномиальное время (если только P = NP). Лучшее, что вы можете сделать, это просто посмотреть на все различные подмножества. Взгляните на задачу subset sum .

Рассмотрим список [0, 1, 5, 6]. Вы будете требовать {0, 5} и {1, 6}, когда лучший ответ на самом деле {0, 1, 5} и {6}.

1 голос
/ 29 августа 2013

Комбинации по комбинации подходов:

import itertools as it

def min_diff_sets(data):
    """
        Parameters:
        - `data`: input list.
        Return:
        - min diff between sum of numbers in two sets
    """

    if len(data) == 1:
        return data[0]
    s = sum(data)
    # `a` is list of all possible combinations of all possible lengths (from 1
    # to len(data) )
    a = []
    for i in range(1, len(data)):
        a.extend(list(it.combinations(data, i)))
    # `b` is list of all possible pairs (combinations) of all elements from `a`
    b = it.combinations(a, 2)
    # `c` is going to be final correct list of combinations.
    # Let's apply 2 filters:
    # 1. leave only pairs where: sum of all elements == sum(data)
    # 2. leave only pairs where: flat list from pairs == data
    c = filter(lambda x: sum(x[0])+sum(x[1])==s, b)
    c = filter(lambda x: sorted([i for sub in x for i in sub])==sorted(data), c)
    # `res` = [min_diff_between_sum_of_numbers_in_two_sets,
    #           ((set_1), (set_2))
    #         ]
    res = sorted([(abs(sum(i[0]) - sum(i[1])), i) for i in c],
            key=lambda x: x[0])
    return min([i[0] for i in res])

if __name__ == '__main__':
    assert min_diff_sets([10, 10]) == 0, "1st example"
    assert min_diff_sets([10]) == 10, "2nd example"
    assert min_diff_sets([5, 8, 13, 27, 14]) == 3, "3rd example"
    assert min_diff_sets([5, 5, 6, 5]) == 1, "4th example"
    assert min_diff_sets([12, 30, 30, 32, 42, 49]) == 9, "5th example"
    assert min_diff_sets([1, 1, 1, 3]) == 0, "6th example"
0 голосов
/ 20 мая 2019

Нет, ваш алгоритм неверен.Ваш алгоритм следует за жадным подходом.Я реализовал ваш подход, и он не прошел этот тестовый случай: (Вы можете попробовать здесь )

Жадный алгоритм:

#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;
}

Тестовый случай:

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

Причиной сбоя жадного алгоритма является то, что он не учитывает случаи, когда брать больший элемент в текущем наборе больших сумм, а в последующем гораздо меньший в наборе больших сумм, можно получить гораздо лучшие результаты.Он всегда старается минимизировать текущую разницу, не изучая и не зная дополнительных возможностей, в то время как в правильном решении вы можете включить элемент в больший набор и включить гораздо меньший элемент позже, чтобы компенсировать эту разницу, так же, как в приведенном выше тестовом примере.

Правильное решение:

Чтобы понять решение, вам нужно разобраться со всеми приведенными ниже проблемами по порядку:

Мой код (та же логика, что и 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;
}
0 голосов
/ 27 апреля 2019

Рекурсивный подход - генерировать все возможные суммы из всех значений массива и проверять какое решение является наиболее оптимальным. Для создания сумм мы либо включаем i-й элемент в набор 1, либо не включаем, т.е. включаем в набор 2.

Сложность времени составляет O (n * sum) как для времени, так и для пространства. 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});
  }

}
0 голосов
/ 20 августа 2016

Во многих ответах упоминается о получении «приблизительного» решения в очень приемлемые сроки. Но поскольку об этом спрашивают в интервью, я не ожидаю, что им нужен алгоритм приближения. Также я не ожидаю, что им также нужен наивный экспоненциальный алгоритм.

Если перейти к задаче, предполагая, что максимальное значение суммы чисел известно, то ее можно решить за полиномиальное время с помощью динамического программирования. Ссылка на эту ссылку https://people.cs.clemson.edu/~bcdean/dp_practice/dp_4.swf

0 голосов
/ 29 января 2016

Это можно решить с помощью BST.
Сначала отсортируйте массив, скажем, arr1
Для начала создайте еще один arr2 с последним элементом arr1 (удалите этот элемент из arr1)

Теперь: повторитешаги до тех пор, пока не произойдет своп.

  1. Проверьте arr1 для элемента, который можно переместить в arr2, используя BST, так что diff меньше MIN diff, найденного до сих пор.
  2. , если мы найдемelement переместите этот элемент в arr2 и снова перейдите к шагу 1.
  3. , если мы не найдем ни одного элемента на вышеуказанных шагах, выполните шаги 1 и 2 для arr2 и arr1.т.е. теперь проверьте, есть ли у нас какой-либо элемент в arr2, который можно переместить в arr1
  4. , продолжайте шаги 1-4, пока нам не понадобится своп.
  5. мы получим решение.

Пример кода Java:

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

/**
 * Divide an array so that the difference between these 2 is min
 * 
 * @author shaikhjamir
 *
 */
public class DivideArrayForMinDiff {

    /**
     * Create 2 arrays and try to find the element from 2nd one so that diff is
     * min than the current one
     */

    private static int sum(List<Integer> arr) {

        int total = 0;
        for (int i = 0; i < arr.size(); i++) {
            total += arr.get(i);
        }

        return total;
    }

    private static int diff(ArrayList<Integer> arr, ArrayList<Integer> arr2) {
        int diff = sum(arr) - sum(arr2);
        if (diff < 0)
            diff = diff * -1;
        return diff;
    }

    private static int MIN = Integer.MAX_VALUE;

    private static int binarySearch(int low, int high, ArrayList<Integer> arr1, int arr2sum) {

        if (low > high || low < 0)
            return -1;

        int mid = (low + high) / 2;
        int midVal = arr1.get(mid);

        int sum1 = sum(arr1);
        int resultOfMoveOrg = (sum1 - midVal) - (arr2sum + midVal);
        int resultOfMove = (sum1 - midVal) - (arr2sum + midVal);
        if (resultOfMove < 0)
            resultOfMove = resultOfMove * -1;

        if (resultOfMove < MIN) {
            // lets do the swap
            return mid;
        }

        // this is positive number greater than min
        // which mean we should move left
        if (resultOfMoveOrg < 0) {

            // 1,10, 19 ==> 30
            // 100
            // 20, 110 = -90
            // 29, 111 = -83
            return binarySearch(low, mid - 1, arr1, arr2sum);
        } else {

            // resultOfMoveOrg > 0
            // 1,5,10, 15, 19, 20 => 70
            // 21
            // For 10
            // 60, 31 it will be 29
            // now if we move 1
            // 71, 22 ==> 49
            // but now if we move 20
            // 50, 41 ==> 9
            return binarySearch(mid + 1, high, arr1, arr2sum);
        }
    }

    private static int findMin(ArrayList<Integer> arr1) {

        ArrayList<Integer> list2 = new ArrayList<>(arr1.subList(arr1.size() - 1, arr1.size()));
        arr1.remove(arr1.size() - 1);
        while (true) {

            int index = binarySearch(0, arr1.size(), arr1, sum(list2));
            if (index != -1) {
                int val = arr1.get(index);
                arr1.remove(index);
                list2.add(val);
                Collections.sort(list2);
                MIN = diff(arr1, list2);
            } else {
                // now try for arr2
                int index2 = binarySearch(0, list2.size(), list2, sum(arr1));
                if (index2 != -1) {

                    int val = list2.get(index2);
                    list2.remove(index2);
                    arr1.add(val);
                    Collections.sort(arr1);

                    MIN = diff(arr1, list2);
                } else {
                    // no switch in both the cases
                    break;
                }
            }
        }

        System.out.println("MIN==>" + MIN);
        System.out.println("arr1==>" + arr1 + ":" + sum(arr1));
        System.out.println("list2==>" + list2 + ":" + sum(list2));
        return 0;
    }

    public static void main(String args[]) {

        ArrayList<Integer> org = new ArrayList<>();
        org = new ArrayList<>();
        org.add(1);
        org.add(2);
        org.add(3);
        org.add(7);
        org.add(8);
        org.add(10);

        findMin(org);
    }
}
0 голосов
/ 21 сентября 2015
HI I think This Problem can be solved in Linear Time on a sorted array , no Polynomial Time is required , rather than Choosing Next Element u can choose nest two Element and decide which side which element to go. in This Way
in this way minimize the difference, let suppose
{0,1,5,6} ,
choose {0,1}
{0} , {1}
choose 5,6
{0,6}, {1,5}
but still that is not exact solution , now at the end there will be difference of sum in 2 array let suppose x
but there can be better solution of difference of (less than x)
for that Find again 1 greedy approach over  sorted half sized array
and move x/2(or nearby) element from 1 set to another or exchange element of(difference x/2) so that difference can be minimized***
0 голосов
/ 25 августа 2015
#include<bits/stdc++.h>
using namespace std;
bool ison(int i,int x)
{
 if((i>>x) & 1)return true;
 return false;
}
int main()
{
// cout<<"enter the number of elements  : ";
    int n;
    cin>>n;
    int a[n];
    for(int i=0;i<n;i++)
    cin>>a[i];
    int sumarr1[(1<<n)-1];
    int sumarr2[(1<<n)-1];
    memset(sumarr1,0,sizeof(sumarr1));
    memset(sumarr2,0,sizeof(sumarr2));
    int index=0;
    vector<int>v1[(1<<n)-1];
    vector<int>v2[(1<<n)-1];

    for(int i=1;i<(1<<n);i++)
    {  
       for(int j=0;j<n;j++)
       {
          if(ison(i,j))
          {
             sumarr1[index]+=a[j];
             v1[index].push_back(a[j]);
          }
          else
          {
             sumarr2[index]+=a[j];
             v2[index].push_back(a[j]);
          }
       }index++;
    }
    int ans=INT_MAX;
    int ii;
    for(int i=0;i<index;i++)
    {
       if(abs(sumarr1[i]-sumarr2[i])<ans)
       {
          ii=i;
          ans=abs(sumarr1[i]-sumarr2[i]);
       }
    }
    cout<<"first partitioned array : ";
    for(int i=0;i<v1[ii].size();i++)
    {
       cout<<v1[ii][i]<<" ";
    }
    cout<<endl;
    cout<<"2nd partitioned array : ";
    for(int i=0;i<v2[ii].size();i++)
    {
       cout<<v2[ii][i]<<" ";
    }
    cout<<endl;
    cout<<"minimum difference is : "<<ans<<endl;
}
0 голосов
/ 05 июля 2015

Возможное решение здесь - https://stackoverflow.com/a/31228461/4955513 Эта Java-программа, по-видимому, решает эту проблему, при условии, что выполняется одно условие - что существует одно и только одно решение проблемы.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...