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

时间:2015-04-27 19:26:37

标签: javascript php algorithm numbers combinations

我几个月来一直试图找到解决方案。这是我的艺术项目。到目前为止,我可以找到部分python和c解决方案,但它们对我的情况没有用...我需要一个有效的解决方案,无论是PHP还是Javascript。

这是一个问题:

  1. 找到N个数字的所有可能组合,应满足以下条件:
    • 数字不会在组合中重复
    • 在其他解决方案中不会以不同的顺序重复数字
    • 仅使用整数
  2. 在一定范围内的整数
  3. 加起来X
  4. 例如:

    1. 找到3个数字的所有组合
    2. 来自1-12
    3. 的所有数字
    4. 最多可添加15个
    5. 计算出的解决方案应吐出:

      expr : LParen expr RParen  # group
           | expr Symbol expr    # binary
           | expr Symbol         # postfix
           | Symbol expr         # prefix
           | Int+                # value
           ;
      
      很明显,这很容易在几分钟内手动完成,但我需要计算更大的范围和更多的数字,所以我需要一个简短的脚本来为我做这个......

      任何帮助将不胜感激!

5 个答案:

答案 0 :(得分:2)

我觉得应对这一挑战的最优雅方式是通过递归。

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组件,每次递归调用时最小值增加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" />

答案 1 :(得分:1)

如果按升序生成列表,则可以避免两种重复。

一个简单的递归解决方案包括选择每个可能的第一个元素,然后递归调用生成器请求可能的延续:也就是说,continuation被限制为只有一个元素,从一个大于所选元素的值开始,并求和所需的总和减去所选元素。

 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。

答案 2 :(得分:1)

下面是一个递归函数,可以完成你想要的任务。

对于您的示例,您可以这样称呼它:

combos(3, 1, 12, 15);

其他功能参数(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

答案 3 :(得分: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;
    }
}

答案 4 :(得分:0)

对于大数字可能效率不高,但你可以使用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);

            }

        }
    }
}