在数组中查找可以求和为目标值的可能数字

时间:2019-02-09 10:22:36

标签: javascript algorithm sum

给定我有一个数字数组,例如[14,6,10]-如何找到可以加到给定目标值的可能组合/对。

例如,我有 [14,6,10] ,我正在寻找目标值为 40 我的预期输出将是

 10 + 10 + 6 + 14
 14 + 14 + 6 + 6
 10 + 10 + 10 + 10

*顺序并不重要

话虽如此,这是我到目前为止所尝试的:

function Sum(numbers, target, partial) {
  var s, n, remaining;

  partial = partial || [];

  s = partial.reduce(function (a, b) {
    return a + b;
  }, 0);

  if (s === target) {
     console.log("%s", partial.join("+"))
  }


  for (var i = 0; i < numbers.length; i++) {
    n = numbers[i];
    remaining = numbers.slice(i + 1);
    Sum(remaining, target, partial.concat([n]));
  }
}

>>> Sum([14,6,10],40);
// returns nothing

>>> Sum([14,6,10],24);
// return 14+10

实际上是无用的,因为只有在该数字只能使用一次求和时才会返回。

那怎么办?

2 个答案:

答案 0 :(得分:5)

只要总和小于所需总和,就可以添加实际索引的值,或者继续下一个索引。

function getSum(array, sum) {
    function iter(index, temp) {
        var s = temp.reduce((a, b) => a + b, 0);
        if (s === sum) result.push(temp);
        if (s >= sum || index >= array.length) return;
        iter(index, temp.concat(array[index]));
        iter(index + 1, temp);
    }

    var result = [];
    iter(0, []);
    return result;
}

console.log(getSum([14, 6, 10], 40));
.as-console-wrapper { max-height: 100% !important; top: 0; }

要获得有限的结果集,可以指定长度并在退出条件下进行检查。

function getSum(array, sum, limit) {
    function iter(index, temp) {
        var s = temp.reduce((a, b) => a + b, 0);
        if (s === sum) result.push(temp);
        if (s >= sum || index >= array.length || temp.length >= limit) return;
        iter(index, temp.concat(array[index]));
        iter(index + 1, temp);
    }

    var result = [];
    iter(0, []);
    return result;
}

console.log(getSum([14, 6, 10], 40, 5));
.as-console-wrapper { max-height: 100% !important; top: 0; }

答案 1 :(得分:4)

TL&DR:跳至第二部分了解真实情况

第一部分

@Nina Scholz对这个基本问题的回答只是向我们展示了算法的美好表现。老实说,这让我很困惑,有两个原因

  1. 当我使用目标[14,6,10,7,3]尝试500时,它对iter函数进行了36,783,575次调用,而不会破坏调用堆栈。但是内存根本没有显示任何重要用途。
  2. 我的动态编程解决方案运行得更快(或可能不会更快),但如果不消耗16GB内存,就无法在上述情况下完成。

因此,我搁置了解决方案,而是开始在开发工具上进一步研究她的代码,发现它既漂亮又有缺点。

首先,我相信这种算法方法(其中包括对递归的非常巧妙的使用)可能值得称呼。这是非常高效的内存,并且仅对构造的结果集使用内存。堆栈会动态地不断增长和缩小,直到达到极限为止。

问题是,尽管效率很高,但仍会进行大量的冗余调用。因此,经过稍微修改后,可以将对iter的36,783,575个调用缩减为20,254,744个……类似于45%,这将产生更快的代码。关键是输入数组必须升序排列。

因此,这是Nina算法的修改版本。 (请耐心等待。完成过程大约需要25秒)

function getSum(array, sum) {
    function iter(index, temp) {cnt++ // counting iter calls -- remove in production code
        var s = temp.reduce((a, b) => a + b, 0);
        sum - s >= array[index]   && iter(index, temp.concat(array[index]));
        sum - s >= array[index+1] && iter(index + 1, temp);
        s === sum                 && result.push(temp);
        return;
    }

    var result = [];
    array.sort((x,y) => x-y); // this is a very cheap operation considering the size of the inpout array should be small for reasonable output.
    iter(0, []);
    return result;
}
var cnt = 0,
    arr = [14,6,10,7,3],
    tgt = 500,
    res;
console.time("combos");
res = getSum(arr,tgt);
console.timeEnd("combos");
console.log(`source numbers are ${arr}
found ${res.length} unique ways to sum up to ${tgt}
iter function has been called ${cnt} times`);

第二部分

尽管我对性能印象深刻,但是我对上述解决方案感到不满意,但没有明确的理由可以提及。它对副作用的工作方式以及很难理解的双重递归,都让我感到困扰。

因此,我来​​回答这个问题。尽管我要在JS中使用函数,但与公认的解决方案相比,这效率要高出许多倍。我们仍然有空间用丑陋的命令性方法使其更快一点。

区别是;

给出数字:[14,6,10,7,3] 目标总和:500

接受的答案:

  • 可能的答案数量:172686
  • 解析时间:26357毫秒
  • 递归调用计数:36783575

下面的答案

  • 可能的答案数量:172686
  • 解析时间:1000毫秒
  • 递归调用计数:542657

function items2T([n,...ns],t){cnt++ //remove cnt in production code
    var c = ~~(t/n);
    return ns.length ? Array(c+1).fill()
                                 .reduce((r,_,i) => r.concat(items2T(ns, t-n*i).map(s => Array(i).fill(n).concat(s))),[])
                     : t % n ? []
                             : [Array(c).fill(n)];
};

var cnt = 0, result;
console.time("combos");
result = items2T([14, 6, 10, 7, 3], 500)
console.timeEnd("combos");
console.log(`${result.length} many unique ways to sum up to 500
and ${cnt} recursive calls are performed`);

另一个重要的一点是,如果给定的数组降序排列,则递归迭代的次数将减少(有时会大大减少),从而使我们可以从柠檬中榨出更多汁。输入数组降序排列时,将上面的内容与下面的内容进行比较。

function items2T([n,...ns],t){cnt++ //remove cnt in production code
    var c = ~~(t/n);
    return ns.length ? Array(c+1).fill()
                                 .reduce((r,_,i) => r.concat(items2T(ns, t-n*i).map(s => Array(i).fill(n).concat(s))),[])
                     : t % n ? []
                             : [Array(c).fill(n)];
};

var cnt = 0, result;
console.time("combos");
result = items2T([14, 10, 7, 6, 3], 500)
console.timeEnd("combos");
console.log(`${result.length} many unique ways to sum up to 500
and ${cnt} recursive calls are performed`);