【问题标题】:All possibilities that are less than X using only Y numbers?仅使用 Y 数小于 X 的所有可能性?
【发布时间】:2013-12-22 18:54:05
【问题描述】:

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

我需要找到小于 100 的所有数字的组合。并非所有数字都必须用于这些组合。示例:2、2+25+37、54+25

如何在 JavaScript 中实现这一点?

谢谢

【问题讨论】:

  • 听起来像是家庭作业 :) 你试过什么?
  • 我很想在这里看到最有效的算法。
  • @adamb 这不是家庭作业。是的,我试过了:(
  • 贴出你试过的代码
  • @zombio 够公平的。这具有发布到 SO 的典型 HW 问题的特征:1. 该示例似乎是一个经典的离散数学 101 问题。 2. 没有发布代码。 3. 你没有表示你理解解决方案需要什么。

标签: javascript arrays math numbers combinations


【解决方案1】:

这是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) 时间(与未优化相同)。在我的测试中,在大小为 20-50 的 0 到 99 的随机数列表上,它的速度要快 2-5 倍(加速与大小成正比,但我不确定使用什么公式)。

【讨论】:

  • 这太酷了。我没有时间真正挑选它,但这是一个非常有趣的优化想法。 +1
  • 很酷,但对于有界输入数字(如示例中 - 不超过 100)来说,动态规划解决方案肯定会快得多。
  • @ciamej 如果您认为自己有更好的答案,请将其作为答案发布。
  • @Saposhiente 转念一想,问题是要找到所有组合,而不仅仅是它们的数量。我错过了。那么可能不可能得到更好的复杂性......
  • O(2^(N/2)) 指的是平均运行时复杂度,对吧?在最坏的情况下,当所有元素加起来小于 X 时,您仍然需要计算 O(2^N) 子集。
【解决方案2】:

所以如果你有一个数字数组:

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


编辑

对不起,你想要 sum 小于 100 的所有集合的集合。kennebec 是对的。放弃第一步过滤,然后修改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

【讨论】:

  • 谢谢,我不知道“电源组”这个词是解决这个问题的关键。我还找到了一个很好的例子here
  • 请注意,如果集合中允许负数,这将无法正常工作。
  • @kennebec - 怎么会这样?我不知道怎么做,我测试了它,它看起来没有错。
  • 顺便说一句,我的问题表述得很糟糕,我的意思是我只想要 SUM 小于 100 的可能性。但是使用您的解决方案,很容易添加最后一步。
  • 在您更新的演示中,您忘记了&lt; 100 检查。已在版本jsfiddle.net/wdhDy/8 中修复
【解决方案3】:

如果您只想获得独特的组合,您可以尝试这样的方法...
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(" "));
        }
    }
}());

【讨论】:

    【解决方案4】:

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

    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) 最后一个元素是单例集当且仅当它小于上界。
    • (B) 包括最后一个元素的所有其他子集都被计算在内,方法是将函数递归地应用到省略该元素的列表中,并通过该元素减少一个新的上界。
    • (C) 所有其他排除最后一个元素的子集都被计算在内,方法是使用相同的上界将函数递归地应用于列表。

    最后,有 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 ]]

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-11-30
      • 2013-01-17
      • 1970-01-01
      • 1970-01-01
      • 2020-09-23
      • 2017-12-02
      • 1970-01-01
      相关资源
      最近更新 更多