Подтвердить что ты не робот

Алгоритм подсчета сумм

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

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

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

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

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

4b9b3361

Ответ 1

Этот вопрос просматривали 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 элементы в массиве, можем ли мы найти сумму поднабора для s?" m[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 нижней границей.

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

Обратите внимание, что в приведенных выше примерах строки таблицы DP отсортированы. Это не должно быть так.

Вот таблица DP для случая вопроса, то есть заданного набора {5, 3, 11, 8, 2}. Для краткости я опустил ложные значения.

┌─────────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┐
│ (index) │  0   │  2   │  3   │  5   │  7   │  8   │  10  │  11  │  13  │  14  │  15  │  16  │
├─────────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┤
│    0    │ true │      │      │      │      │      │      │      │      │      │      │      │
│    5    │ true │      │      │ true │      │      │      │      │      │      │      │      │
│    3    │ true │      │ true │ true │      │ true │      │      │      │      │      │      │
│    11   │ true │      │ true │ true │      │ true │      │ true │      │ true │      │ true │
│    8    │ true │      │ true │ true │      │ true │      │ true │ true │ true │      │ true │
│    2    │ true │ true │ true │ true │ true │ true │ true │ true │ true │ true │ true │ true │
└─────────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┘

Ниже приведена реализация на JavaScript, которая выведет целевой набор {5, 11}:

var subSetSum = function(input, sum) {

    let y = input.length;
    let x = sum;

    if(input.length === 0) return 0;

    let d = [];

    //fill the rows
    for (let i = 0; i <= y; i++) {
      d[i] = [];
      d[i][0] = true;
    }
    
    for (let j = 1; j <= y; j++) { //j row
      for (let i = 1; i <= x; i++) { //i column
      let num = input[j-1];
        if(num === i) {
          d[j][i] = true;
        } else if(d[j-1][i]) {
          d[j][i] = true;
        } else if (d[j-1][i-num]) {
          d[j][i] = true;
        }
      }
    }
    
    //console.table(d); //uncomment to see the table
    if(!d[y][x]) return null;

    let searchedSet = [];
    for(let j=input.length, i=sum; j>0 && i != 0; j--) {
      if(input[j-1] !== i) {
        while(d[j-1][i]) { // go up
          j--;
        }
      }
      searchedSet.push(input[j-1]);
      i = i-input[j-1];
    }

    return searchedSet;
};

console.log('searched set:'+ JSON.stringify(subSetSum([5, 3, 11, 8, 2], 16)));

Ответ 2

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

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

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

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

Ответ 3

Я предлагаю прочитать алгоритм 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 или Weakly NP-Complete.

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

Ответ 4

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

Ответ 5

Кажется, я опоздал на вечеринку, вот мои два цента. Мы создадим 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];
    }
}

Ответ 6

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

Ответ 7

Решение DP с одномерным массивом (порядок обработки массива DP имеет значение здесь).

bool subsetsum_dp(vector<int>& v, int sum)
{
    int n = v.size();
    const int MAX_ELEMENT = 100;
    const int MAX_ELEMENT_VALUE = 1000;
    static int dp[MAX_ELEMENT*MAX_ELEMENT_VALUE + 1]; memset(dp, 0, sizeof(dp));

    dp[0] = 1;

    for (int i = 0; i < n; i++)
    {
        for (int j = MAX_ELEMENT*MAX_ELEMENT_VALUE; j >= 0; j--)
        {
            if (j - v[i] < 0) continue;
            if (dp[j - v[i]]) dp[j] = 1; 
        }
    }

    return dp[sum] ? true : false;
}

Ответ 8

пусть M - сумма всех элементов. Заметим, что K <= M

let m be a Boolean array [0...M]
set all elements of m to be False
m[0]=1
for all numbers in the set let a[i] be the ith number
    for j = M to a[i]
        m[j] = m[j] | m[j-a[i]];

Тогда просто проверьте для m [k]

Ответ 9

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-му элементу. Если это не способствует, нам нужно искать "значение суммы" в остальных элементах.

Ответ 10

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

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

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

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

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

  • Если самый правый элемент равен сумме, которую вы пытаетесь проверить для возврата true
  • Отложите левую, если это не оставит пустой set, удалить самый правый элемент из вашего отсортированного массива
  • Если в вашем наборе остался один элемент, и это не сумма return false
  • Вместо того, чтобы правильно рекурсивно проверять сумму всех элементов в массив q, если X <= B <= Y, то возвращает true, если не возвращает false
  • Если левое поддерево или правая рекурсия вернулась true, верните true родительскому

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

Ответ 11

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

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

Ответ 12

Рекурсивное решение с 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) т.е. если первый элемент составляет подмножество, сумма которого равна данной сумме.

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