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

孤街浪徒 提交于 2019-12-06 04:46:24

问题


Say I have these numbers: [2, 25, 37, 54, 54, 76, 88, 91, 99] (these are random)

And I need to find all combinations of those numbers that are less than 100. Not all numbers have to be used in these combinations. Examples: 2, 2+25+37, 54+25

How can I achieve this in JavaScript?

Thanks


回答1:


So if you have an array of numbers:

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

First filter the array to just that which is less than 100

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

Now you need to find the power set of those numbers.

It looks like there's a sample of code here that will accomplish that.

Excerpt

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;
}

So you'd take

var powerSet = powerset(filtered);

And as some sugar, you could format the result nicely with join

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

or if you really want it output as a set of all sets, this would technically be more correct :)

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

Here's a WORKING DEMO


EDIT

Sorry, you want the set of all sets whose sum is less than 100. kennebec is right. Ditch the filtering first step, and then modify the powerset method thus, using reduce to quickly see if an array's sum is less than 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;
}

Here's an UPDATED DEMO




回答2:


This is a modified version of the Subset sum problem. Taking the power set is the brute force solution, and while simple, is inefficient for large lists, taking O(2^N) time. Subset-sum is NP-complete, so you can't solve it in less than exponential time, but if you divide and conquer, you can solve it faster in the average case (but not the worst case)1. What you do is, divide the array into two halves and run the powerset function (from Adam's answer) on each half, except you save the sum of the array with the array (in fact, saving the sum of the array creates a huge performance boost even if you don't split the array, as it lets you eliminate lots of redundant addition):

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);
}

Then, you sort each half's power set by the sum, sorting in opposite directions

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

Now, you can go through the two lists and return each combination of arrays whose total sum is less than 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. The decision version of this solution (which tells you, is there a solution?) runs in O(2^(N/2)) time. I expect this runs in O(2^(N/2)) if there are O(1) solutions, and O(2^N) time (the same as unoptimized) in the worst case where every subset is a solution. In my tests, it is faster by factors of 2-5 on lists of size 20-50 of random numbers from 0 to 99 (Speedup is proportional to size, but I am unsure of by what formula).



回答3:


If you want to get only unique combinations you can try something like this...
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:


Here's a recursive approach, that also applies to only non-negative array elements:

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);

Here's the jfiddle: http://jsfiddle.net/bceHr/4/

The base case is the single-element list, where the answer is itself if and only if the element is less than the upper bound.

The recursive step is divided into 3 mutually exclusive and complete cases, marked A, B, and C above:

  • (A) The last element is a singleton set if and only if it is less than the upper_bound.
  • (B) All other subsets which include the last element are counted, by recusively applying the function to the list omitting the element, and a new upper_bound decreased by that element.
  • (C) All other subsets which exclude the last element are counted, by recursively applying the function to the list using the same upper_bound.

Lastly, there are 26 such combinations. Since 54 is included twice, it is repeated in the output as well:

[[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]]



来源:https://stackoverflow.com/questions/20406674/all-possibilities-that-are-less-than-x-using-only-y-numbers

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!