找出一定范围内N个不重复数的所有可能组合,加起来等于X
find all possible combinations of N non-repeating numbers within a certain range that add up to X
几个月来我一直在努力寻找解决方案。这是我的一个艺术项目。到目前为止,我可以找到部分 python 和 c 解决方案,但它们对我的情况没有用...我需要 PHP 或 Javascript.[=11= 中的工作解决方案]
问题是:
- 求出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]
显然这很容易在几分钟内手动完成,但我需要计算更大的范围和更多的数字,所以我需要一个简短的脚本来为我做这件事...
如有任何帮助,我们将不胜感激!
如果按升序生成列表,将避免这两种重复。
一个简单的递归解决方案包括选择每个可能的第一个元素,然后递归调用生成器请求可能的延续:也就是说,延续仅限于少一个元素,以大于所选元素的值开始元素,然后求和到所需的总和减去所选元素。
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
超出最大实用值,或者至少超出保守估计来改进上述内容。或者,您可以在递归调用 returns 空集后停止递增 i。
对于大数可能效率不高,但是使用 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);
}
}
}
}
下面是一个递归函数,可以执行您想要的操作。
对于您的示例,您可以这样称呼它:
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 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
) 传递回递归函数以拆分为 [=17] 来实现的=] 组件,每次递归调用时最小值增加 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" />
这里有一个稍微优化的解决方案。通过在范围内从最大到最小迭代,跳过所有太大的可能性变得非常容易。
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;
}
}
几个月来我一直在努力寻找解决方案。这是我的一个艺术项目。到目前为止,我可以找到部分 python 和 c 解决方案,但它们对我的情况没有用...我需要 PHP 或 Javascript.[=11= 中的工作解决方案]
问题是:
- 求出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]
显然这很容易在几分钟内手动完成,但我需要计算更大的范围和更多的数字,所以我需要一个简短的脚本来为我做这件事...
如有任何帮助,我们将不胜感激!
如果按升序生成列表,将避免这两种重复。
一个简单的递归解决方案包括选择每个可能的第一个元素,然后递归调用生成器请求可能的延续:也就是说,延续仅限于少一个元素,以大于所选元素的值开始元素,然后求和到所需的总和减去所选元素。
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
超出最大实用值,或者至少超出保守估计来改进上述内容。或者,您可以在递归调用 returns 空集后停止递增 i。
对于大数可能效率不高,但是使用 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);
}
}
}
}
下面是一个递归函数,可以执行您想要的操作。
对于您的示例,您可以这样称呼它:
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 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
) 传递回递归函数以拆分为 [=17] 来实现的=] 组件,每次递归调用时最小值增加 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" />
这里有一个稍微优化的解决方案。通过在范围内从最大到最小迭代,跳过所有太大的可能性变得非常容易。
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;
}
}