说我有这些数字:[2,25,37,54,54,76,88,91,99](这些是随机的)
我需要找到小于100的那些数字的所有组合。并非所有数字都必须在这些组合中使用。例子:2,2 + 25 + 37,54 + 25
如何在JavaScript中实现这一目标?
由于
答案 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 :(得分: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('}{') + '} }');
修改
抱歉,您想要总和小于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;
}
答案 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:
最后,有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 ]