簡體   English   中英

僅使用Y數的所有可能性小於X?

[英]All possibilities that are less than X using only Y numbers?

說我有這些數字:[2,25,37,54,54,76,88,91,99](這些是隨機的)

我需要找到那些小於100的數字的所有組合。並非所有數字都必須在這些組合中使用。 例子:2,2 + 25 + 37,54 + 25

我怎樣才能在JavaScript中實現這一目標?

謝謝

這是子集和問題的修改版本。 獲取功率集是一種強力解決方案,雖然簡單,但對於大型列表來說效率低,需要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++;
}

工作基准將此與非拆分解決方案進行比較

  1. 此解決方案的決策版本(告訴您,是否有解決方案?)在O(2 ^(N / 2))時間內運行。 我希望如果存在O(1)解,則在O(2 ^(N / 2))中運行,並且在每個子集是解的最壞情況下,O(2 ^ N)時間(與未優化相同)。 在我的測試中,從0到99的隨機數大小為20-50的列表上的因子為2-5更快(加速與大小成正比,但我不確定通過什么公式)。

所以如果你有一組數字:

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

首先將數組過濾到小於100的數組

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

現在您需要找到這些數字的冪集。

它看起來像有一個代碼示例這里 ,將實現這一目標。

摘抄

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('}{') + '} }');

這是一個工作演示


編輯

對不起,你想要總和小於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;
}

這是一個更新的演示

如果你想只獲得獨特的組合,你可以試試這樣的......
的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(" "));
        }
    }
}());

這是一種遞歸方法,也只適用於非負數組元素:

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]

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM