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

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

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

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

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

Ответ 1

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

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

В статье "Самая сложная проблема " американского ученого дан отличный анализ проблемы. Вы должны пройти и прочитать это!

Ответ 2

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

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

Ответ 3

Комбинации по сочетаниям:

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 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"

Ответ 4

Одно небольшое изменение: отмените порядок - начните с наибольшего числа и снижайтесь. Это минимизирует ошибку.

Ответ 5

Вы сортируете свой поднабор в порядке убывания или порядке возрастания?

Подумайте об этом так: массив {1, 3, 5, 8, 9, 25}

Если бы вы разделились, у вас было бы {1,8,9} = 18 {3,5,25} = 33

Если бы он был отсортирован в порядке убывания, это получилось бы намного лучше

{25,1} = 26 {9,8,5,3} = 25

Итак, ваше решение в основном правильное, просто нужно убедиться, что сначала берет наибольшие значения.

ИЗМЕНИТЬ: прочитайте сообщение tskuzzy. Шахта не работает

Ответ 6

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

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

Если массив содержит отрицательные числа выше, не будет работать.

Ответ 7

После многих мыслей и комбинации я придумал следующее решение.

1. Sort and add elements at the same time.(To reduce one more iteration on array.)
2. Do (Sum/2) and find out median of sorted array (array/2)(median can be used to optimize more)
3. Pick up highest and lowest one by one until its near or equal to (sum/2)

Это решение будет работать и для отрицательных значений.

Ответ 8

Попробуйте следующее:

 partition(i, A, B) = min(partition(i+1, A U A[i], B),
                          partition(i+1, A, B U A[i])) where i< n  
                      Absolute(Sigma(A) - Sigma(B)) where i = n                                                

  n : number of elements in Original Array

Ответ 9

Это вариация проблемы суммарного суммирования и подмножества. В задаче суммы подмножества, заданной n положительных целых чисел и значении k, мы должны найти сумму подмножества, значение которой меньше или равно k. В приведенной выше задаче мы задали массив, здесь мы должны найти подмножество, сумма которого меньше или равна total_sum (сумма значений массива). Итак сумма подмножества может быть найдена с использованием вариации алгоритма ранца, принимая прибыль в качестве заданных значений массива. И окончательный ответ total_sum-дп [п] [total_sum/2]. Посмотрите нижеприведенный код для понимание.

#include<iostream>
#include<cstdio>
using namespace std;
int main()
{
        int n;
        cin>>n;
        int arr[n],sum=0;
        for(int i=1;i<=n;i++)
        cin>>arr[i],sum+=arr[i];
        int temp=sum/2;
        int dp[n+1][temp+2];
        for(int i=0;i<=n;i++)
        {
            for(int j=0;j<=temp;j++)
            {
                if(i==0 || j==0)
                dp[i][j]=0;
                else if(arr[i]<=j)
                dp[i][j]=max(dp[i-1][j],dp[i-1][j-arr[i]]+arr[i]);
                else
                {
                dp[i][j]=dp[i-1][j];
                }
            }
        }
        cout<<sum-2*dp[n][temp]<<endl;
}

Ответ 10

Это можно решить с помощью BST.
Сначала соберите массив say arr1
Чтобы начать создание другого arr2 с последним элементом arr1 (удалите этот элемент из arr1)

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

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

Пример кода 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);
    }
}

Ответ 11

Рекурсивный подход состоит в том, чтобы генерировать все возможные суммы из всех значений массива и проверять, какое решение является наиболее оптимальным. Для генерации сумм мы либо включаем 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});
  }

}

Ответ 12

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

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

#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

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

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

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

Мой код (та же логика, что и эта):

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

Ответ 13

int ModDiff(int a, int b)
{
    if(a < b)return b - a;
    return a-b;
}

int EqDiv(int *a, int l, int *SumI, int *SumE)
{
    static int tc = 0;
    int min = ModDiff(*SumI,*SumE);
    for(int i = 0; i < l; i++)
    {
            swap(a,0,i);
            a++;
            int m1 = EqDiv(a, l-1, SumI,SumE);
            a--;
            swap(a,0,i);

            *SumI = *SumI + a[i];
            *SumE = *SumE - a[i];
            swap(a,0,i);
            a++;
            int m2 = EqDiv(a,l-1, SumI,SumE);
            a--;
            swap(a,0,i);
            *SumI = *SumI - a[i];
            *SumE = *SumE + a[i];

            min = min3(min,m1,m2);

    }
    return min;

}

вызовите функцию с SumI = 0 и SumE = sumof всех элементов в a. Это решение O (n!) Вычисляет способ разделения данного массива на две части, так что разница минимальна. Но определенно не практично из-за n! сложность времени, направленная на улучшение этого с помощью DP.

Ответ 14

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

  • Сортируйте основной массив и разделите его на 2 команды.
  • Затем начните делать команду равной по смене и замене элементов из одного массива на другой, основываясь на условиях, упомянутых в коде.

Если разница в разнице сумм меньше минимального числа большего массива (массив с большей суммой), тогда смещение элементов из большего массива в меньший массив. Смена происходит с условие, этот элемент из большего массива со значением, меньшим или равным разности. Когда все элементы из большего массива больше разницы, происходит остановка переключения и свопинг. Я просто заменяю последние элементы массива (его можно сделать более эффективным, найдя, какие два элемента будут заменены), но все же это сработало. Дайте мне знать, не удалось ли эта логика в любом случае.

public class SmallestDifference {
static int sum1 = 0, sum2 = 0, diff, minDiff;
private static List<Integer> minArr1;
private static List<Integer> minArr2;
private static List<Integer> biggerArr;

/**
 * @param args
 */
public static void main(String[] args) {
    SmallestDifference sm = new SmallestDifference();
    Integer[] array1 = { 2, 7, 1, 4, 5, 9, 10, 11 };
    List<Integer> array = new ArrayList<Integer>();
    for (Integer val : array1) {
        array.add(val);
    }
    Collections.sort(array);
    CopyOnWriteArrayList<Integer> arr1 = new CopyOnWriteArrayList<>(array.subList(0, array.size() / 2));
    CopyOnWriteArrayList<Integer> arr2 = new CopyOnWriteArrayList<>(array.subList(array.size() / 2, array.size()));
    diff = Math.abs(sm.getSum(arr1) - sm.getSum(arr2));
    minDiff = array.get(0);
    sm.updateSum(arr1, arr2);
    System.out.println(arr1 + " : " + arr2);
    System.out.println(sum1 + " - " + sum2 + " = " + diff + " : minDiff = " + minDiff);
    int k = arr2.size();
    biggerArr = arr2;
    while (diff != 0 && k >= 0) {
        while (diff != 0 && sm.findMin(biggerArr) < diff) {
            sm.swich(arr1, arr2);
            int sum1 = sm.getSum(arr1), sum2 = sm.getSum(arr2);
            diff = Math.abs(sum1 - sum2);
            if (sum1 > sum2) {
                biggerArr = arr1;
            } else {
                biggerArr = arr2;
            }
            if (minDiff > diff || sm.findMin(biggerArr) > diff) {
                minDiff = diff;
                minArr1 = new CopyOnWriteArrayList<>(arr1);
                minArr2 = new CopyOnWriteArrayList<>(arr2);
            }
            sm.updateSum(arr1, arr2);
            System.out.println("Shifting : " + sum1 + " - " + sum2 + " = " + diff + " : minDiff = " + minDiff);
        }
        while (k >= 0 && minDiff > array.get(0) && minDiff != 0) {
            sm.swap(arr1, arr2);
            diff = Math.abs(sm.getSum(arr1) - sm.getSum(arr2));
            if (minDiff > diff) {
                minDiff = diff;
                minArr1 = new CopyOnWriteArrayList<>(arr1);
                minArr2 = new CopyOnWriteArrayList<>(arr2);
            }
            sm.updateSum(arr1, arr2);
            System.out.println("Swapping : " + sum1 + " - " + sum2 + " = " + diff + " : minDiff = " + minDiff);
            k--;
        }
        k--;
    }
    System.out.println(minArr1 + " : " + minArr2 + " = " + minDiff);
}

private void updateSum(CopyOnWriteArrayList<Integer> arr1, CopyOnWriteArrayList<Integer> arr2) {
    SmallestDifference sm1 = new SmallestDifference();
    sum1 = sm1.getSum(arr1);
    sum2 = sm1.getSum(arr2);
}

private int findMin(List<Integer> biggerArr2) {
    Integer min = biggerArr2.get(0);
    for (Integer integer : biggerArr2) {
        if(min > integer) {
            min = integer;
        }
    }
    return min;
}

private int getSum(CopyOnWriteArrayList<Integer> arr) {
    int sum = 0;
    for (Integer val : arr) {
        sum += val;
    }
    return sum;
}

private void swap(CopyOnWriteArrayList<Integer> arr1, CopyOnWriteArrayList<Integer> arr2) {
    int l1 = arr1.size(), l2 = arr2.size(), temp2 = arr2.get(l2 - 1), temp1 = arr1.get(l1 - 1);
    arr1.remove(l1 - 1);
    arr1.add(temp2);
    arr2.remove(l2 - 1);
    arr2.add(temp1);
    System.out.println(arr1 + " : " + arr2);
}

private void swich(CopyOnWriteArrayList<Integer> arr1, CopyOnWriteArrayList<Integer> arr2) {
    Integer e;
    if (sum1 > sum2) {
        e = this.findElementJustLessThanMinDiff(arr1);
        arr1.remove(e);
        arr2.add(e);
    } else {
        e = this.findElementJustLessThanMinDiff(arr2);
        arr2.remove(e);
        arr1.add(e);
    }
    System.out.println(arr1 + " : " + arr2);
}

private Integer findElementJustLessThanMinDiff(CopyOnWriteArrayList<Integer> arr1) {
    Integer e = arr1.get(0);
    int tempDiff = diff - e;
    for (Integer integer : arr1) {
        if (diff > integer && (diff - integer) < tempDiff) {
            e = integer;
            tempDiff = diff - e;
        }
    }
    return e;
}

}

Ответ 15

Возможное решение здесь - fooobar.com/questions/126707/... Эта Java-программа, похоже, решает эту проблему, если выполняется одно условие - существует одно и единственное решение проблемы.

Ответ 16

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

Ответ 17

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

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

Ответ 18

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***