仅使用Y数的所有可能性小于X?

时间:2013-12-05 17:27:18

标签: javascript arrays math numbers combinations

说我有这些数字:[2,25,37,54,54,76,88,91,99](这些是随机的)

我需要找到小于100的那些数字的所有组合。并非所有数字都必须在这些组合中使用。例子:2,2 + 25 + 37,54 + 25

如何在JavaScript中实现这一目标?

由于

4 个答案:

答案 0 :(得分:6)

这是Subset sum problem的修改版本。获取功率集是一种强力解决方案,虽然简单,但对于大型列表来说效率低,需要O(2 ^ N)时间。子集和是NP完全的,所以你不能在低于指数的时间内解决它,但是如果你分而治之,你可以在一般情况下更快地解决它(但不是最坏的情况) 1 。你做的是,将数组分成两半并在每一半上运行powerset函数(来自Adam的答案),除了你用数组保存数组的总和(实际上,保存数组的总和会产生巨大的性能)即使你没有拆分数组也会提升,因为它可以消除大量的冗余添加):

var sum = ps[j].sum + arr[i] //huge optimization! don't redo all the addition
if (sum < 100) { //don't include this check if negative numbers are allowed
    arrCandidate.sum = sum;
    ps.push(arrCandidate);
}

然后,您按总和对每一半的功率进行排序,按相反方向排序

ps1.sort(function(b,a){return a.sum-b.sum;});
ps2.sort(function(a,b){return a.sum-b.sum;});

现在,您可以浏览这两个列表并返回总和小于100的数组的每个组合:

var pos1 = 0;
var pos2 = -1;
while (pos1 < ps1.length) {
    var arr1 = ps1[pos1];
    while (pos2 + 1 < ps2.length && ps2[pos2+1].sum+arr1.sum < 100) {
        pos2++;
    }
    for (var i = pos2; i >= 0; i--) {
        result.push(arr1.concat(ps2[i]));
    }
    pos1++;
}

Working benchmark comparing this to a non-splitting solution

  1. 此解决方案的决策版本(告诉您,是否有解决方案?)在O(2 ^(N / 2))时间内运行。我希望如果存在O(1)解,则在O(2 ^(N / 2))中运行,并且在每个子集是解的最坏情况下,O(2 ^ N)时间(与未优化相同)。在我的测试中,从0到99的随机数大小为20-50的列表上的因子为2-5更快(加速与大小成正比,但我不确定通过什么公式)。

答案 1 :(得分:5)

所以如果你有一组数字:

var arr = [2, 25, 37, 54, 54, 76, 88, 91, 99]

首先将数组过滤到小于100

的数组
var filtered = arr.filter(function(val){ return val < 100; });

现在您需要找到这些数字的幂集。

看起来有一个代码here的示例可以实现这一目标。

摘录

function powerset(arr) {
    var ps = [[]];
    for (var i=0; i < arr.length; i++) {
        for (var j = 0, len = ps.length; j < len; j++) {
            ps.push(ps[j].concat(arr[i]));
        }
    }
    return ps;
}

所以你需要

var powerSet = powerset(filtered);

作为一些糖,您可以使用join

很好地格式化结果
console.log('{' + powerSet.join('}{') + '}');

或者如果你真的希望它输出为一组所有集合,这在技术上会更正确:)

console.log('{ {' + powerSet.join('}{') + '} }');

这是WORKING DEMO


修改

抱歉,您想要总和小于100的所有套装。肯尼贝克是对的。抛弃过滤的第一步,然后修改powerset方法,使用reduce快速查看数组的总和是否小于100:

function powerset(arr) {
    var ps = [[]];
    for (var i=0; i < arr.length; i++) {
        for (var j = 0, len = ps.length; j < len; j++) {
            var arrCandidate = ps[j].concat(arr[i]);
            if (arrCandidate.reduce(function(p, c){ return p + c; }) < 100)
                ps.push(arrCandidate);
        }
    }
    return ps;
}

这是UPDATED DEMO

答案 2 :(得分:1)

如果您只想获得独特的组合,可以尝试这样的事情......
jsFiddle

(function () {
    "use strict";
    var numbers = [2, 25, 37, 54, 54, 76, 88, 91, 99],
        combinations = [];

    (function () {
        var temp = [],
            len = numbers.length,
            sum = 0;

        for (var i = 0; i < len; i++) {
            temp.length = 0;
            sum = numbers[i];

            if (sum < 100) {
                temp.push(sum);
                add(temp);

                for (var j = 0; j < len; j++) {
                    if (numbers[j] >= 100 || i === j) {
                        continue;
                    }
                    sum += numbers[j];

                    if (sum < 100) {
                        temp.push(numbers[j]);
                        add(temp);
                    } else {
                        sum -= numbers[j];
                    }
                }
            }
        }
    }());

    function add(val) {
        var contains = false,
            temp = null;

        val.sort(function (a, b) {
            return a - b;
        });

        temp = val.join(" ");
        if (combinations.length === 0) {
            combinations.push(temp.split(" "));
            return;
        }

        for (var i = 0; i < combinations.length; i++) {
            if (combinations[i].join(" ") === temp) {
                contains = true;
            }
        }
        if (!contains) {
            combinations.push(temp.split(" "));
        }
    }
}());

答案 3 :(得分:0)

这是一种递归方法,也只适用于非负数组元素:

function subset_sum( list, upper_bound )
{
  if( list.length == 1 ) return list[0] < upper_bound ? [list] : [];
  var new_list = list.slice(0); // copy list
  var elem = new_list.pop();
  var combo = elem < upper_bound ? [[elem]] : []; // A
  if( combo.length )
  {
    var lists = subset_sum( new_list, upper_bound-elem ); // B
    combo = combo.concat( lists.map(function(a) { return a.concat(elem); }) );
  }
  return combo.concat(subset_sum( new_list, upper_bound )); // C
}

var arr = [2, 25, 37, 54, 54, 76, 88, 91, 99];
var combos = subset_sum(arr,100);

这是jfiddle:http://jsfiddle.net/bceHr/4/

基本案例是单元素列表,当且仅当元素小于上限时,答案本身才是。

递归步骤分为3个互斥和完整的案例,标记为A,B和C:

  • (A)当且仅当它小于upper_bound时,最后一个元素是单例集。
  • (B)包括最后一个元素的所有其他子集,通过将该函数重复应用于省略该元素的列表,并按该元素减少新的upper_bound来计数。
  • (C)通过使用相同的upper_bound以递归方式将函数应用于列表来计算排除最后一个元素的所有其他子集。

最后,有26种这样的组合。由于包括两次54,因此在输出中也会重复:

  

[[99],[91],[2,91],[88],[2,88],[76],[2,76],[54],[37,54],[2 ,37,54],[25,54],[2,25,54],[2,54],[54],[37,54],[2,37,54],[25,54], [2,25,54],[2,54],[37],[25,37],[2,25,37],[2,37],[25],[2,25],[2 ]