найти все возможные комбинации из N неповторяющихся чисел в определенном диапазоне, которые складываются в X
Я уже несколько месяцев пытаюсь найти решение этой проблемы. это для моего художественного проекта. до сих пор я мог найти частичные решения python и c, но они бесполезны для моего случая... мне нужно рабочее решение либо на PHP, либо на Javascript.
Вот в чем вопрос:
- найти все возможные комбинации из N чисел, при этом должно выполняться следующее:
- числа не повторяются в комбинации
- числа не повторяются в других решениях в другом порядке
- используются только целые числа
- в пределах некоторого диапазона целых чисел
- , которые складываются в X
Например:
- найти все комбинации из 3 чисел
- внутри всех чисел от 1-12
- что в сумме дает 15
Вычисленное решение должно выплюнуть:
[1,2,12]
[1,3,11]
[1,4,10]
[1,5,9]
[1,6,8]
[1,7,7] = EXAMPLE OF WRONG OUTPUT, NO REPEATING NUMBERS WITHIN COMBINATION
[1,8,6] = EXAMPLE OF WRONG OUTPUT, NO REPEATING NUMBERS IN OTHER SOLUTIONS (see [1,6,8])
[2,3,10]
[2,4,9]
[2,5,8]
[2,6,7]
[3,4,8]
[3,5,7]
[4,5,6]
Очевидно, что это было легко сделать за пару минут вручную, но мне нужно вычислить гораздо больший диапазон и многое другое. цифры, поэтому мне нужен короткий сценарий, чтобы сделать это для меня...
Любая помощь будет оценена!
5 ответов:
Я чувствую, что самый элегантный способ справиться с этой проблемой-это рекурсия.
function getCombos(target, min, max, n) { var arrs = []; if (n === 1 && target <= max) { arrs.push([target]); } else { for (var i = min; i < target / n && i <= max; i++) { var arrays = getCombos(target - i, i + 1, max, n - 1); for (var j = 0; j < arrays.length; j++) { var array = arrays[j]; array.splice(0, 0, i); arrs.push(array); } } } return arrs; }
Объяснение
Это работает путем восхождения от минимального числаi
как первого элемента в каждом массиве и передачи остатка (target-i
) обратно в рекурсивную функцию, которая будет разбита на компонентыn-1
, причем минимум увеличивается на единицу с каждым рекурсивным вызовом.Обратите внимание, что числа в первом индексе каждого массива никогда не будут превышать15 = (1 + 14) = 1 + (2 + 12) 15 = (1 + 14) = 1 + (3 + 11) 15 = (1 + 14) = 1 + (4 + 10) ... 15 = (1 + 14) = 1 + (6 + 8) 15 = (2 + 13) = 2 + (3 + 10) 15 = (2 + 13) = 2 + (4 + 9) ... 15 = (4 + 11) = 4 + (5 + 6)
target/n
, гдеtarget
- это число, которое вы суммируете, аn
- это число элементов в массиве. (Таким образом, при разбиении 15 на 3 компонента первая колонка всегда будет меньше 5.) Это справедливо и для других столбцов, ноn
уменьшается на 1 по мере роста индекса массива. Знание этого позволяет нам рекурсировать, не требуя дополнительных параметров для нашей рекурсивной функции.Рабочий Пример
Проверьте фрагмент ниже, чтобы увидеть его в действие.
function getCombos(target, min, max, n) { var arrs = []; if (n === 1 && target <= max) { arrs.push([target]); } else { for (var i = min; i < target / n && i <= max; i++) { var nextTarget = target - i; var nextMin = i + 1; var arrays = getCombos(nextTarget, nextMin, max, n - 1); for (var j = 0; j < arrays.length; j++) { var array = arrays[j]; array.splice(0, 0, i); arrs.push(array); } } } return arrs; } document.getElementById("submit").onclick = function () { var target = document.getElementById("target").value; var min = document.getElementById("min").value; var max = document.getElementById("max").value; var n = document.getElementById("n").value; var result = getCombos(+target, +min, +max, +n); document.getElementById("output").innerHTML = result.join("<br/>"); };
.table { display:table; table-layout:fixed; width:100%; } .table-row { display:table-row; } .cell { display:table-cell; }
<div class="table"> <div class="table-row"> <div class="cell">Target:</div> <div class="cell"> <input id="target" type="text" value=15> </div> <div class="cell">n:</div> <div class="cell"> <input id="n" type="text" value=3> </div> </div> <div class="table-row"> <div class="cell">Min:</div> <div class="cell"> <input id="min" type="text" value=1> </div> <div class="cell">Max:</div> <div class="cell"> <input id="max" type="text" value=12> </div> </div> </div> <input id="submit" type="button" value="submit" /> <div id="output" />
Если вы создадите списки в порядке возрастания, вы избежите повторения обоих видов.
Простое рекурсивное решение состоит в том, чтобы выбрать каждый возможный первый элемент, а затем рекурсивно вызвать генератор, запрашивающий возможные продолжения: то есть продолжения ограничены наличием одного меньшего элемента, начиная со значения, большего, чем выбранный элемент, и суммируя до желаемой суммы минус выбранный элемент.Partitions(min, size, total): if size is 1: if total < min: return nothing else return the list [total] for each value i between min and total: get the set of lists Partitions(i+1, size-1, total-i) add i to the beginning of each list return all the lists.
Вышесказанное может быть улучшено путем не позволяя
i
выйти за пределы наибольшей практической ценности или, по крайней мере, за пределы консервативной оценки. Кроме того, вы можете остановить инкрементирование i после того, как рекурсивный вызов вернет пустой набор.
Ниже приведена рекурсивная функция, которая делает то, что вы хотите.
Для вашего примера, вы бы назвали это так:
combos(3, 1, 12, 15);
Дополнительные параметры функции (
a
,running
,current
) Следите за текущим состоянием и можете игнорировать:var arr= []; function combos(num, min, max, sum, a, running, current) { var i; a= a || []; running= running || 0; current= current || min; for(i = current ; i <= max ; i++) { if(num===1) { if(i+running===sum) { arr.push(a.concat(i)); } } else { combos(num-1, min, max, sum, a.concat(i), i+running, i+1); } } };
Скрипка
Вот немного оптимизированное решение. Перебегая от наибольшего к наименьшему в диапазоне, становится довольно легко пропустить все возможности, которые слишком велики.
function combos(size, start, end, total, solution) { var solutions = []; solution = solution || []; if (size === 1) { if (start <= total && end >= total) { solutions.push(solution.concat([total])); } return solutions; } else { while (end > start) { var newTotal = total - end; solutions = solutions.concat( combos( size - 1, start, Math.min(end - 1, newTotal), newTotal, solution.concat([end]) ) ); end--; } return solutions; } }
Может быть неэффективно для больших чисел, но с помощью 3 вложенных
for()
циклов вы можете сделать -$t=20; // up to X $s=$t-3; // sets inner loop max $r=$t/3; // sets middle loop max $q=$r-1; // sets outer loop max $results= array(); // array to hold results for($x=1;$x<=$q;$x++){ for($y=($x+1);$y<=$r;$y++){ for($z=($x+2);$z<=$s;$z++){ // if sum == max && none are the same value if(($x+$y+$z)==$t && ($x!=$y && $x!=$z && $y!=$z)){ $results[]=array($x,$y,$z); } } } }