递归以在一个加起来目标值的范围内找到n个数字

时间:2017-02-20 23:04:00

标签: javascript recursion sum subset-sum

我编写了一个函数,可以找到所有两个数字的集合,它们给出一个目标值的总和,给定从0到x的数字范围。我试图以某种方式重写它,以便你可以获得给定长度n(不仅是2)的结果,但是当加在一起时n个数字将等于目标。

例如,如果范围是1到5,并且您想要找到最多为7的所有三个数字的集合,那么答案将是:

[[1,1,5], [1,2,4], [1,3,3], [2,2,3]]

看起来解决方案是使用递归函数,但我无法弄清楚如何执行此操作。我已经在StackOverflow上查看了几个子集和示例,但似乎没有一个匹配这个特定场景。

这不是作业问题。任何帮助,将不胜感激。这是我的findPairs函数:

function findPairs(arr, target) {
    var pairs = [];
    var first = 0;
    var last = arr.length-1;
    while (first <= last) {
      var sum = arr[first] + arr[last];
      if (sum === target) {
        pairs.push([arr[first], arr[last]]);
        first ++;
        last--;
      }
      else if (sum < target) {
        first++;
      }
      else {
        last--;
      }
    }
    return pairs;
  }

  var sample = _.range(11);
  console.log(JSON.stringify(findPairs(sample,12)));
  // Returns
  // [[2,10],[3,9],[4,8],[5,7],[6,6]]

此示例使用lodash _.range函数。在这里小提琴:https://jsfiddle.net/tbarmann/muoms1vL/10/

3 个答案:

答案 0 :(得分:2)

你确实可以使用递归。将第三个参数定义为您要查找的子数组的长度,并在该函数内定义递归函数,如下所示:

function findSums(arr, target, count) {
    var result = [];
    function recurse(start, leftOver, selection) {
        if (leftOver < 0) return; // failure
        if (leftOver === 0 && selection.length == count) {
            result.push(selection); // add solution
            return;
        }
        for (var i = start; i < arr.length; i++) {
            recurse(i, leftOver-arr[i], selection.concat(arr[i]));
        }
    }
    recurse(0, target, []);
    return result;
}
// Demo
var result = findSums([1,2,3,4,5], 7, 3);
console.log(result);   
.as-console-wrapper { max-height: 100% !important; top: 0; }

说明

解决方案不要求输入数组具有连续数字(范围):您可以传递它[3,6,4,2,1]。返回的子数组将按顺序保留所选元素,例如:[3,3,3][3,4,2][6,2,1]可以是针对该示例输入以3个值进行目标9的解决方案。 / p>

数字必须都是非负数。如果要允许负值,则必须从代码中删除优化if (leftOver < 0) return;

答案 1 :(得分:1)

&#13;
&#13;
function sumPermutations(target, range, number) {
  var result = [];

  function combo(left, group, sum) {
    if(sum > target) return null;
    if (left == 0) {
      if(sum == target) return group;
      return null;
    }
    
    for (var i = range.min; i <= range.max; i++) {
       var r = combo(left - 1, group.concat(i), sum + i);
      if (r)
        result.push(r);
    }
  }
  combo(number, [], 0);

  return result;
}

console.log(sumPermutations(7, {min: 1, max: 5}, 3));
&#13;
&#13;
&#13;

注意:这会产生重复的结果(所有的permutaions包括那些具有不同订单的permutaions)。您可以通过对数组进行排序并连接其项并将其散列到哈希对象中来删除重复项。

答案 2 :(得分:1)

嗯,你可能正在寻找的是Dynamic Programming。这是一种在数学上定义问题和递归解决方案的方法。然后,您尝试使用memoization找到解决方案。

我会创建一个辅助函数,其参数为(range, target, lengthOfResult)。然后做一些事情:

func helper(arr, target, lengthOfResult){
    if(taget == 0) continue;
    for(int i=1; i<arr.length-1; i++){
        if(taget - arr[i] < 0) return [];
        var sub_results = helper(arr, taget - arr[i], lengthOfResult - 1)
        foreach(var res in sub_results){
            result.concat([arr[i]] + res);
        }
    }
    return result;
}

因此,您正在将辅助功能的问题更改为&#34;向我提供length-1总计为taget-arr[i]&#34;的所有列表,并附加到arr[i] 。然后使用它来为每个arr[i]构建结果。

基本上,每次迭代的长度都会减少,所以函数会在某个时刻终止。你从现在的数字中减去你的数字。因此,您的最终结果将累加到所需的目标。

  • 请注意,此算法仅适用于正数。如果你可以允许底片,你应该删除第一个for循环中的if。
  • 关于memoization,如果你想只允许使用一个数字,你就可以使用一个数组。如果允许结果中重复出现的数字(如示例中所示),则可能需要一个网格;水平的目标,垂直的lengthOfResult。然后在每次调用辅助方法的开始时,检查是否已经计算了该值。这将为您节省一些递归调用,并使算法不是指数级的。