简体   繁体   English

在一定范围内找到N个非重复数字的所有可能组合,这些总数加起来为X

[英]find all possible combinations of N non-repeating numbers within a certain range that add up to X

i have been trying to find a solution to this for several months now. 我几个月来一直在努力寻找解决方案。 it is for an art project of mine. 这是我的一个艺术项目。 so far i could find partial python and c solutions, but they are of no use for my case... i need a working solution either in PHP or Javascript. 到目前为止,我可以找到部分的python和c解决方案,但对于我的情况来说它们是没有用的...我需要使用PHP或Javascript的有效解决方案。

this is the question: 这是问题:

  1. find all possible combinations of N numbers, the following should be satisfied: 找到所有可能的N个数字的组合,应满足以下条件:
    • numbers are not repeated within a combination 数字不能在组合中重复
    • numbers are not repeated in other solutions in different order 数字在其他解决方案中不会以不同的顺序重复
    • only whole numbers are being used 仅使用整数
  2. within a certain range of whole numbers 在一定范围内的整数
  3. that add up to X 总计X

for example: 例如:

  1. find all combinations of 3 numbers 找到3个数字的所有组合
  2. within all numbers from 1-12 在1到12之间的所有数字内
  3. that add up to 15 总计15

the computed solution should spit out: 计算出的解决方案应吐出:

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

obviously that was easy to do in a couple of minutes by hand, but i need to calculate a much bigger range and much more numbers, so i need a short script to do this for me... 显然,手动进行几分钟很容易,但是我需要计算更大的范围和更多的数字,所以我需要一个简短的脚本来为我做这件事...

any help would be appreciated! 任何帮助,将不胜感激!

I feel like the most elegant way to handle this challenge is via recursion. 我觉得处理此挑战的最优雅方法是通过递归。

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

Explanation 说明

This works by climbing up from the minimum number i as the first item in each array, and passing the remainder ( target-i ) back into the recursive function to be split into n-1 components, with the minimum increased by one with each recursive call. 通过从最小值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)

Note that the numbers at the first index of each array will never exceed target/n , where target is the number you're summing to, and n is the number of items in the array. 请注意,每个数组的第一个索引处的数字永远不会超过target/n ,其中target是您求和的数字, n是数组中的项目数。 (So when splitting 15 into 3 components, the first column will always be less than 5.) This holds true for the other columns as well, but n is reduced by 1 as the index of the array climbs. (因此,将15拆分为3个部分时,第一列将始终小于5。)其他列也是如此,但是随着数组索引的增加, n会减少1。 Knowing this allows us to recurse without requiring extra parameters on our recursive function. 知道这一点后,我们便可以进行递归,而无需在递归函数上添加额外的参数。

Working Example 工作实例

Check out the snippet below to see it in action. 查看下面的代码片段,以查看实际效果。

 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" /> 

If you generate the lists in ascending order, you will avoid both kinds of repetition. 如果以升序生成列表,则将避免两种重复。

An easy recursive solution consists of selecting each possible first element, and then recursively calling the generator requesting the possible continuations: that is, the continuations are restricted to having one fewer element, to starting with a value greater than the chosen element, and summing to the desired sum minus the chosen element. 一个简单的递归解决方案包括选择每个可能的第一个元素,然后递归调用生成器以请求可能的延续:即,延续被限制为具有更少的元素,以大于所选元素的值开始,然后求和所需总和减去所选元素。

 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. 

The above can be improved by not letting i get beyond the largest practical value, or at least beyond a conservative estimate. 通过不让i超出最大的实际价值或至少超出保守的估计,可以改善上述情况。 Alternatively, you can stop incrementing i after a recursive call returns an empty set. 或者,您可以在递归调用返回空集后停止i的递增。

Below is a recursive function that does what you want. 下面是执行所需功能的递归函数。

For your example, you would call it like this: 对于您的示例,您可以这样称呼它:

combos(3, 1, 12, 15);

The additional function parameters ( a , running , current ) keep track of the current state and can be ignored: 附加功能参数( arunningcurrent )跟踪当前状态,可以忽略:

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

Fiddle 小提琴

Here's a slightly optimized solution. 这是一个稍微优化的解决方案。 By iterating from largest to smallest in the range, it becomes pretty easy to skip all the possibilities that are too large. 通过从范围中的最大到最小进行迭代,跳过所有太大的可能性变得非常容易。

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

Might not be efficient for large numbers, but using 3 nested for() loops you can do - 对于大量而言,效率可能不高,但是使用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);

            }

        }
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM