javascript - find subset that gives maximum sum under a certain limit (subset sum )

痴心易碎 提交于 2019-12-08 18:30:06

问题


I have an array with some integer values, and I need to get a subset of them that gives me the maximum sum that is inferior to a given value.
So let's say I have this array:

[40, 138, 29, 450]

I would like to get a subset of this array that maximize the sum but is inferior to a limit given by the user, let's say 250. In this case it should return [139, 40, 29].
I had a look at this question and related answer, and tried to use the code given, but I didn't understand it very well. Anyway I've tried it, setting the min to 0 and the max to the limit given, but it keeps returning me "5" that is not correct, since the limit is like 300 and the numbers in my array are all over 50.
I couldn't find anything that could help me, so I'm asking if anyone could give me some code or pseudocode to understand how to do this.


回答1:


Basically you could either add the element at the index to a temporary array or not. Then check if the index reaches the lenght of the array or if the sum is greater than the wanted sum, then either check the sum and add the temp array to the result set, or not.

Proceed until all indices are visited.

function getCombinations(array, sum) {
    function add(a, b) { return a + b; }

    function fork(i, t) {
        var r = (result[0] || []).reduce(add, 0),
            s = t.reduce(add, 0);
        if (i === array.length || s > sum) {
            if (s <= sum && t.length && r <= s) {
                if (r < s) {
                    result = [];
                }
                result.push(t);
            }
            return;
        }
        fork(i + 1, t.concat([array[i]]));
        fork(i + 1, t);
    }

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

console.log(getCombinations([40, 138, 29, 450], 250));
.as-console-wrapper { max-height: 100% !important; top: 0; }



回答2:


A fast and compact solution:

function maxSum(input, limit) {
    const sums = {};
    let max = 0;

    const collectSums = (n, i, values) => {
        for (; i < input.length; i++) {
            const sum = n + input[i];
            if (sum <= limit) {
                values.push(input[i]);
                if (sum >= max && values.length > 1) {
                    max = sum;
                    sums[max] = values.slice(); // https://jsperf.com/copying-an-array
                }
                collectSums(sum, i + 1, values);
            }
        }
        values.pop();
    };

    collectSums(0, 0, []);

    return sums[max] || [];
}

Apart from the necessary iterations of the input this solution tries to keep complexity low by not using costly array operations. Only a found subset has to be copied to keep track of possible combinations. Still, there are probably more clever solutions possible to improve performance.

The method will return the last found combination, this means that two input lists with the same values in different order might yield different results:

maxSum([1, 4, 200, 5], 205) == [200, 5];
maxSum([5, 200, 1, 4], 205) == [200, 1, 4];

If you want all possible combinations replace this line:

sums[max] = values.slice(); // https://jsperf.com/copying-an-array

with this:

sums[max] = sums[max] || [];
sums[max].push(values.slice());

All combinations are then returned:

maxSum([1, 4, 200, 5], 205) == [[1, 4, 200], [200, 5]];

But note that this will always return an array of arrays, even when there is only one possibility:

maxSum([40, 138, 29, 450], 250) == [[40, 138, 29]];



回答3:


Here's a brute force solution. First we get every possible combination of values from the original array, take their sum, and see which of those gets us the highest value without overflowing the given maximum.

var ary = [40, 138, 29, 450];

// Function to construct a power set. A power set is just the set of
// all possible subsets of some given set.
function makePowerSet(ary) {
    powerSet = [];
    for (let ps = 1; ps <= Math.pow(2, ary.length); ps++) {
        subset = [];
        for (let i = 0; i < ary.length; i++) {
            if (ps & Math.pow(2, i)) subset.push(ary[i]);
        }
        powerSet.push(subset);
    }
    return powerSet;
}

// Function to calculate the sum of an array.
function getSum(ary) {
   return ary.reduce((sum, cur) => {
      return sum + cur;
   }, 0);
}

function getSubsetBelow(val, ary) {
    let bestSoFar;
    let bestSoFarSum = 0;
    for (let subset of makePowerSet(ary)) {
        const sum = getSum(subset);
        if (sum > val) continue;
        if (sum > bestSoFarSum) {
            bestSoFar = subset;
            bestSoFarSum = sum;
        }
    }
    
    console.log("Got a best sum value of", bestSoFarSum, "with the subset", bestSoFar);
}

getSubsetBelow(250, ary)

This seems very similar to the knapsack problem, which is NP-hard, so I don't know if you'll ever be able to find an efficient algorithm for this. However, there are definitely a few optimizations that can be made to what I've written here, for example, any element of the array already greater than the limit can't be part of the solution (easy way to eliminate 450).



来源:https://stackoverflow.com/questions/47891953/javascript-find-subset-that-gives-maximum-sum-under-a-certain-limit-subset-su

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