Ответ 1
Этот вопрос просматривали 36000+ раз, но я не вижу достаточного ответа, который подробно объясняет алгоритм с помощью логики. Поэтому я решил сделать попытку.
Предположение:
Для простоты сначала я сделал предположение, что входной набор X
содержит только положительные целые числа, а k
положительный. Однако мы можем настроить алгоритм для обработки отрицательных целых чисел и в случае, если k
отрицателен.
Логика:
Ключом к этому алгоритму или на самом деле любой проблеме DP является решение проблемы и начало просто с базового варианта. Затем мы можем опираться на базовый вариант, используя некоторые знания, которые нам известны:
- мы знаем, что если набор
X
пуст, то мы не сможем суммировать любое значениеk
. - Если набор
X
содержитk
, то он имеет сумму поднабора, равнуюk
. - мы знаем, что если подмножество набора
x1
, которое является подмножеством суммыX
вk1
, тоX
будет иметь подмножество, которое суммирует вk1
, а именноx1
. - у нас есть набор
X = {x1, x1, x3, ......., xn, xn+1}
. Мы знаем, что у него есть поднабор суммы вk1
, еслиx1 = {x1, x1, x3, ......., xn}
имеет поднабор суммы вk - k1
.
Пример для иллюстрации 1,2,3,4:
- это легко. если у вас есть пустой набор {}. вы не можете иметь подмножество таким образом Вы не можете иметь какую-либо подмножество.
Набор
X = {4}
имеет подмножество суммы 4, потому что 4 само является частью набораскажем, у вас есть набор
x1 = {1,3,5}
, который является подмножеством набораX = {1,3,5,2,8}
. еслиx1
имеет сумму поднабора вk1 = 8
, то это означает, чтоX
также имеет сумму поднабора в 8, потому чтоx1
является подмножествомX
- скажем, у вас есть набор
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)));