Алгоритм сумм подмножеств - PullRequest
43 голосов
/ 05 декабря 2010

Я работаю над этой проблемой:

Задача суммы поднабора принимает в качестве входных данных набор X = {x1, x2 ,…, xn} из n целых чисел и другое целое число K.Проблема состоит в том, чтобы проверить, существует ли подмножество X' из X, элементы которого составляют K, и находит подмножество, если оно есть.Например, если X = {5, 3, 11, 8, 2} и K = 16, то ответом будет YES, поскольку подмножество X' = {5, 11} имеет сумму 16.Реализуйте алгоритм для суммы подмножеств, чье время выполнения составляет не менее O(nK).

Обратите внимание на сложность O(nK).Я думаю, что динамическое программирование может помочь.

Я нашел экспоненциальный алгоритм времени, но он не помогает.

Может кто-нибудь помочь мне решить эту проблему?

Ответы [ 12 ]

45 голосов
/ 01 августа 2017

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

Предположение:

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

Логика:

Ключ к этому алгоритму или на самом деле любая проблема DP заключается в том, чтобы разбить проблему и начать просто с базового случая. тогда мы можем опираться на базовый случай, используя некоторые знания что мы знаем:

  1. мы знаем, что если набор X пуст, то мы не сможем суммировать любое значение k.
  2. Если набор X содержит k, то у него есть поднабор суммы k.
  3. мы знаем, что если подмножество набора x1, которое является подмножеством X, суммируется с k1, тогда X будет иметь подмножество, которое будет иметь сумму k1, а именно x1.
  4. у нас есть набор X = {x1, x1, x3, ......., xn, xn+1}. Мы знаем, что подмножество имеет значение k1, если x1 = {x1, x1, x3, ......., xn} имеет подмножество суммы k - k1.

Пример для иллюстрации 1,2,3,4:

  1. это легко. если у вас есть пустой набор {}. вы не можете иметь подмножество таким образом Вы не можете иметь какую-либо подмножество.
  2. Набор X = {4} имеет подмножество суммы 4, потому что 4 само является частью набора

  3. скажем, у вас есть набор x1 = {1,3,5}, который является подмножеством набора X = {1,3,5,2,8}. если x1 имеет подмножество суммы k1 = 8, то это означает, что X также имеет подмножество суммы 8, потому что x1 является подмножеством X

  4. скажем, у вас есть набор X = {1,3,5,2,19}, и мы хотим знать, имеет ли он подмножество суммой до 20. Это делает, и один способ узнать, может ли это быть x1 = {1,3,5,2}, может суммироваться с (20 - 19) = 1. Поскольку x1 имеет подмножество суммы 1, тогда, когда мы добавим 19 к множеству x1 мы можем взять это новое число 1 + 19 = 20, чтобы создать желаемую сумму 20.

Динамически построить матрицу Здорово! Теперь давайте воспользуемся четырьмя логиками и начнем строить с базового варианта. Мы собираемся построить матрицу m. Мы определяем:

  • матрица m имеет i+1 строки и k + 1 столбцы.

  • Каждая ячейка матрицы имеет значение true или false.

  • m [i] [s] возвращает true или false, чтобы указать ответ на этот вопрос: «Используя первые i элементов в массиве, можем ли мы найти подмножество суммы в sm[i][s] возвращает true для да и false для нет

(обратите внимание, что ответ из Википедии или большинство людей строят функцию m (i, s), но я подумал, что матрица - это простой способ понять динамическое программирование. Она хорошо работает, когда в наборе или массиве есть только положительные числа) Однако функция route лучше, потому что вам не нужно иметь дело с индексом вне диапазона, сопоставлять индекс массива и сумму с матрицей .....)

Построим матрицу на примере:

X = {1,3,5,2,8}
k = 9

Мы собираемся строить матрицу построчно. В конечном итоге мы хотим знать, что ячейка m [n] [k] содержит true или false.

Первая строка: Логика 1. говорит нам, что первая строка матрицы должна быть false.

   0 1 2 3 4 5 6 7 8 9
   _ _ _ _ _ _ _ _ _ _
0| F F F F F F F F F F
1|
2|
3|
4|
5|

Вторая строка и выше: Затем для второй строки или выше мы можем использовать логику 2,3,4, чтобы помочь нам заполнить матрицу.

  • логика 2 говорит нам, что m[i][s] = (X[i-1] == s) Remembermebr m [i] относится к i-му элементу в X, который является X [i-1]
  • логика 3 говорит нам, что m[i][s] = (m[i-1][s]) это смотрит на указанную выше ячейку.
  • логика 4 говорит нам, что m[i][s] = (m[i-1][s - X[i-1]]) это взгляд на строку выше и слева от X [i-1] ячеек.

Если любой из них true, тогда m[i][s] равен true, в противном случае false. так что мы можем переписать 2,3,4 в m[i][s] = (m[i-1][s] || a[i-1] == s || m[i-1][s - a[i-1]])

Используйте приведенную выше логику для заполнения матрицы m. В нашем примере это выглядит так.

   0 1 2 3 4 5 6 7 8 9
   _ _ _ _ _ _ _ _ _ _
0| F F F F F F F F F F
1| F T F F F F F F F F
2| F T F T T F F F F F 
3| F T F T T T T F T T
4| F T T T T T T T T T 
5| F T T T T T T T T T

Теперь воспользуйтесь матрицей для ответа на ваш вопрос:

посмотрите на m[5][9], это оригинальный вопрос. Используя первые 5 элементов (которые являются всеми элементами), можем ли мы найти подмножество суммы до 9 (k)? и ответ указывается той ячейкой, которая является true

Вот код:

import java.util.*;

public class SubSetSum {

    public static boolean subSetSum(int[] a, int k){

        if(a == null){
            return false;
        }

        //n items in the list
        int n = a.length; 
        //create matrix m
        boolean[][] m = new boolean[n + 1][k + 1]; //n + 1 to include 0, k + 1 to include 0 

        //set first row of matrix to false. This also prevent array index out of bounds: -1
        for(int s = 0; s <= k; s++){
            m[0][s] = false;
        }

        //populate matrix m
        for(int i = 1; i <= n; i++){
            for(int s = 0; s <= k; s++){    
                if(s - a[i-1] >= 0){ //when it goes left we don't want it to go out of bounds. (logic 4)
                    m[i][s] = (m[i-1][s] || a[i-1] == s || m[i-1][s - a[i-1]]); 
                } else {
                    m[i][s] = (m[i-1][s] || a[i-1] == s);
                }       

            }
        }

        //print matrix
        print(m);

        return m[n][k];

    }

    private static void print(boolean[][] m){
        for(int i = 0; i < m.length; i++){
            for(int j = 0; j < m[i].length; j++){
                if(m[i][j]){
                    System.out.print("T");
                } else {
                    System.out.print("F");
                }           
            }
            System.out.print("\n");
        }
    }

    public static void main(String[] args){
        int[] array = {1,3,5,2,8};
        int k = 9;

        System.out.println(subSetSum(array,k));

    }
}

Для построения матрицы m требуется O ((n + 1) (k + 1)), то есть O (nk). кажется, это должно быть многочленом, но это не так! Это на самом деле псевдополином. Читайте об этом здесь

Опять же, это работает, только если вход содержит только положительные числа. Вы можете легко настроить его для работы с отрицательными числами. Матрица будет по-прежнему иметь n + 1 рядов, но B - A + 1 столбцов. Где B - верхняя граница, а A - нижняя граница (+1, чтобы включить ноль). Матрица все равно будет. Вам придется сместить s с нижней границей.

Довольно сложно объяснить проблему ДП поверх текста от начала до конца. Но я надеюсь, что это поможет тем, кто пытается понять эту проблему.

18 голосов
/ 05 декабря 2010

Так как все ваши числа выглядят как положительные, вы можете решить это с помощью динамического программирования:

Начать будет логический массив possible размера K + 1 с первым значением true, остальное false.I-е значение будет представлять, возможно ли получить поднабор суммы i.Для каждого числа n в вашем наборе проведите цикл по массиву possible и, если i-е значение равно true, установите также значение i + nth на true.

В конце, если значение kth вpossible верно, тогда вы можете сформировать подмножество суммы k.Задача решена за время O (NK).

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

8 голосов
/ 05 декабря 2010

Я бы предложил прочитать алгоритм Wiki . Алгоритм существует там, см. Решение для динамического программирования с псевдополиномиальным временем для решения O(P*n). Решение не является полиномиальным временем, оно является полиномиальным по (p, n), но оно не является полиномиальным по n + log P (размер ввода) и поскольку P может быть очень большим, например, 2 ^ n, решение P * n = (2 ^ n) * n в общем случае не является решением за полиномиальное время, но когда p ограничено некоторой полиномиальной функцией из n - алгоритм полиномиального времени.

Это проблема NPC, но для нее существует алгоритм Pseudo polynomial time, который относится к weakly NP-Complete задачам. Также есть проблемы Strongly NP-Complete, что означает: Вы не можете найти какой-либо алгоритм pseudo polynomial time для них, если P = NP, и эта проблема не находится в этом диапазоне проблем, так что как-то легко.

Я сказал, что это настолько просто, насколько это возможно, но это не точное определение задач «Сильно NP-Complete» или «Слабо NP-Complete».

Подробнее см. Гэри и Джонсон глава 4.

3 голосов
/ 14 октября 2018

Кажется, я опоздал на вечеринку, вот мои два цента. Мы создадим boolean[] solution[n+1][k+1] так, что solution[i][j] будет true, если, используя первые i элементы (индекс 0 до i-1), мы можем получить сумму j из набора; остальное false. Мы вернем solution[k][n] наконец:

Мы можем вывести следующие пункты:

  1. если сумма равна нулю, то всегда возможный ответ (пустой набор) для любого количества элементов. Так что все верно.
  2. если множество пусто, мы не можем иметь никакого подмножества, следовательно, нет никакого способа получить какой-либо K. Поэтому никогда не будет возможного ответа. Все ложно.
  3. если подмножество X1 (подмножество X без последнего элемента в X) имеет подмножество для k, то X также имеет его, которое равно X1. Например. для X1 = {1,3,5} и k = 8, если X1 имеет подмножество-сумму, то X = {1,3,5,7} также имеет подмножество-сумму
  4. Для i / p установить X = {1,3,5,7,19} и k = 20, если X хочет знать возможность подмножества суммы для 20, тогда это происходит, если x1 = {1,3,5 , 7} может иметь подмножество-сумму 20-19, т. Е. 1. Применяется, только если k> = 19, т. Е. Последний элемент в X.

Исходя из вышеизложенного, мы можем легко написать алгоритм, как показано ниже.

public class SubSetSum {
    boolean[][] solution; 
    int[] input;
    int k;

    public SubSetSum(int[] input, int targetSum) {
        this.input = input;
        this.k = targetSum;
        this.solution = new boolean[input.length+1][k+1];
    }

    public boolean subsetSum() {
        int n = input.length;

        for (int i = 0; i <= n; i++) {     //case 1
            solution[i][0] = true;
        }

        for (int j = 0; j <= k; j++) {    // case 2
            solution[0][j] = false;
        }

        for (int i = 1; i <= n; i++) {                  // n times
            for (int j = 1; j <= k; j++) {              // k times and time complexity O(n*k)
                if(solution[i-1][j]) {
                    solution[i][j] = solution[i-1][j];      // case 3
                    continue;
                }
                if(j >= input[i-1])  {                       // case 4
                    solution[i][j] = solution[i-1][j-input[i-1]];
                }
            }
        }
        return solution[n][k];
    }
}
3 голосов
/ 05 декабря 2010

Не существует известного алгоритма для суммы подмножеств, которая в общем случае работает меньше O (2 ^ (n / 2)).

2 голосов
/ 24 сентября 2012
void subsetSum (int arr[], int size, int target) {
  int i, j ;
  int **table ;
  table = (int **) malloc (sizeof(int*) * (size+1)) ;
  for ( i = 0 ; i <= size ; i ++ ) {
    table[i] = (int *) malloc (sizeof(int) * (target+1)) ;
    table[i][0] = 1 ;
  }
  for ( j = 1 ; j <= target ; j ++ )
    table[0][j] = 0 ;
  for ( i = 1 ; i <= size ; i ++ ) {
    for ( j = 1 ; j <= target ; j ++ )
      table[i][j] = table[i-1][j] || (arr[i-1] <= j && table[i-1][j-arr[i-1]] ) ;
  } 
  if ( table[size][target] == 1 )
    printf ( "\ntarget sum found\n" ) ; 
  else printf ( "\nTarget sum do not found!\n" ) ;
  free (table) ;
}
0 голосов
/ 25 июня 2019

Рекурсивное решение с n ^ 2 временной сложностью

public void solveSubsetSum(){
    int set[] = {2,6,6,4,5};
            int sum = 9;
            int n = set.length;

            // check for each element if it is a part of subset whose sum is equal to given sum
            for (int i=0; i<n;i++){
                if (isSubsetSum(set, sum, i, n)){
                    Log.d("isSubset:", "true") ;
                    break;
                }
                else{
                    Log.d("isSubset:", "false") ;
                }
                k=0; // to print time complexity pattern
            }
        }

private boolean isSubsetSum(int[] set, int sum, int i, int n) {

            for (int l=0;l<k; l++){
            System.out.print("*"); 
            // to print no of time is subset call for each element
        }
        k++;
        System.out.println();     
        if (sum == 0){
            return true;
        }

        if (i>=n){
            return false;
        }

        if (set[i] <= sum){ 
        // current element is less than required sum then we have to check if rest of the elements make a subset such that its sum is equal to the left sum(sum-current element)
            return isSubsetSum(set, sum-set[i], ++i, n);
        }
        else { //if current element is greater than required sum
            return isSubsetSum(set, sum, ++i, n);
        }
   }

Сложность в худшем случае: O (n ^ 2)

Лучший случай: O (n), т.е. если первый элемент создает подмножество, сумма которого равна данной сумме.

Поправьте меня, если я ошибаюсь, чтобы вычислить сложность времени здесь.

0 голосов
/ 26 апреля 2019
function subsetsum(a, n) {
    var r = [];
    for (var i = parseInt(a.map(function() { return 1 }).join(''), 2); i; i--) {
        var b = i.toString(2).split('').reverse().map(function(v, i) {
            return Number(v) * a[i]
        }).filter(Boolean);
        if (eval(b.join('+')) == n) r.push(b);
    }
    return r;
}

var a = [5, 3, 11, 8, 2];
var n = 16;
console.log(subsetsum(a, n)); // -> [[3, 11, 2], [5, 3, 8], [5, 11]]

Грубая сила - забудьте сортировку, попробуйте все комбо, и анализатор eval превосходит Array.reduce (и он работает также с отрицательными числами).

0 голосов
/ 02 марта 2018

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

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

X = сумма отрицательных элементов

Y = сумма положительных элементов

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

  1. Если самый правый элемент равен сумме, которую вы пытаетесь проверить на возвращаемое значение true
  2. Рекурсировать влево, если при этом не будет оставлено пустое множество, отбросьтесамый правый элемент из вашего отсортированного массива
  3. Если в вашем наборе остался один элемент, а это не сумма, возвращаемая ложь
  4. Вместо повторения вправо, проверьте сумму всех элементов в массивеq, если X <= B <= Y, тогда верните true, если не вернете false </li>
  5. Если левое поддерево или правая 'рекурсия' вернули true, тогда верните true в tродитель

Приведенные выше ответы более подробны и точны, но для очень широкого взгляда на то, как это следует разыграть, нарисуйте двоичное дерево.Что это говорит о времени выполнения?

0 голосов
/ 18 февраля 2017
boolean hasSubset(int arr[],int remSum,int lastElem){
    if(remSum==0) return true;
    else if(remSum!=0 && lastElem<0) return false;

    if(arr[lastElem]>remSum) return hasSubset(arr, remSum, lastElem-1);
    else return (hasSubset(arr, remSum, lastElem-1) ||hasSubset(arr, remSum-arr[lastElem], lastElem-1));
}

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

...