найти все возможные комбинации из 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); } } } }