返回数组中所有可能的数字组合,其总和小于或等于n

时间:2014-02-08 00:53:25

标签: javascript combinations permutation

var a = [1,3,6,10,-1];
function combinations(array, n) {
}

combinations(a, 9) // should return...
[[1], [3], [6], [-1], [1,3], [1,6], [1,-1], [3,6], [3,-1], [6, -1], [10, -1], [1,3,-1], [3,6,-1], [1,6,-1], [1,3,6,-1]]

也许我错过了一些正确的答案,但你明白了。真的很想知道如何解决这个问题!

9 个答案:

答案 0 :(得分:8)

我想说这里的问题是取一个数组的幂集,并将其过滤到只有总和大于一定数的元素。

集合的幂集是该集合的所有子集的集合。 (说快五倍,你将成为一名数学家)

例如,[1]的幂集为[[], [1]][1, 2]的幂集为[[], [1], [2], [1, 2]]

首先,我要定义一个powerSet函数,如下所示:

var powerSet = function (arr) {

    // the power set of [] is [[]]
    if(arr.length === 0) {
        return [[]];
    }

    // remove and remember the last element of the array
    var lastElement = arr.pop();

    // take the powerset of the rest of the array
    var restPowerset = powerSet(arr);


    // for each set in the power set of arr minus its last element,
    // include that set in the powerset of arr both with and without
    // the last element of arr
    var powerset = [];
    for(var i = 0; i < restPowerset.length; i++) {

        var set = restPowerset[i];

        // without last element
        powerset.push(set);

        // with last element
        set = set.slice(); // create a new array that's a copy of set
        set.push(lastElement);
        powerset.push(set);
    }

    return powerset;
};

然后我将定义一个函数,它接受数组的幂集,并且只包含总和小于或等于某个量的元素:

var subsetsLessThan = function (arr, number) {

    // all subsets of arr
    var powerset = powerSet(arr);

    // subsets summing less than or equal to number
    var subsets = [];

    for(var i = 0; i < powerset.length; i++) {

        var subset = powerset[i];

        var sum = 0;
        for(var j = 0; j < subset.length; j++) {
            sum += subset[j];
        }

        if(sum <= number) {
            subsets.push(subset);
        }
    }

    return subsets;
};

这在大型阵列上可能不会很快,但它适用于小型阵列。

看起来它为console.log(subsetsLessThan([1,3,6,10,-1], 9));

提供了正确的答案

编辑:关于此处实施的电源设置功能的更多信息

[]的唯一子集是[],因此[]的幂集是仅包含[]的集合。那将是[[]]

如果您传入ifpowerSet函数中的初始[[]]语句会立即返回[]

var powerSet = function (arr) {

    if(arr.length === 0) {
        return [[]];
    }

如果传入一个至少包含一个元素的集合,powerSet函数首先删除最后一个元素。例如,如果您在powerSet上致电[1, 2],则变量lastElement将设置为2arr将设置为[1]

    var lastElement = arr.pop();

然后powerSet函数递归调用自身以获得列表“休息”的幂集。如果您已通过[1, 2],则会将restPowerset分配给powerSet([1]) [[], [1]]

    var restPowerset = powerSet(arr);

我们定义一个变量,该变量将保存传入的内容的幂集,[1, 2]

    var powerset = [];

我们遍历restPowerset中的每一组。

    for(var i = 0; i < restPowerset.length; i++) {

        var set = restPowerset[i];

[1]的任何子集也是[1, 2]的子集,因此我们将其添加到列表中。也就是说,[][1]都是[1, 2]的子集。

        powerset.push(set);

如果您将元素2添加到[1]的任何子集,这也是[1, 2]的子集,那么我们将其添加到列表中。 [2][1, 2]都是[1, 2]的子集。

        set = set.slice(); // copy the array
        set.push(lastElement); // add the element
        powerset.push(set);

这就是全部。此时,变量powerset[[], [2], [1], [1, 2]]。退货吧!

    }

    return powerset;
};

答案 1 :(得分:3)

暴力O(N * 2 N )解决方案,其中N = a.length < 31

这使用索引i作为filter的位字段,在每次迭代中将a的元素放入子列表中。

var a = [1,3,6,10,-1];

function combinations(array, n) {
    var lists = [], M = 1<<array.length;
    for( var i = 1 ; i < M ; ++i ) {
        var sublist = array.filter(function(c,k){return i>>k & 1});
        if( sublist.reduce(function(p,c){return p+c},0) <= n )
            lists.push(sublist);
    }
    return lists;
}

console.log(JSON.stringify(combinations(a,9)));
  

[[1],[3],[1,3],[6],[1,6],[3,6],[ - 1],[1,-1],[3, - 1],[1,3,-1],[6,-1],[1,6,-1],[3,6,-1],[1,3,6,-1],[10 ,-1]]

答案 2 :(得分:3)

与Matt的答案类似,但使用Array.filter()和Array.reduce()打包。在该示例中,变量mask从1增加到32-1(因为阵列长度是5并且count = 1 <&lt; 5,这是32)。对每个掩码增量过滤数组,产生一个新的数组或排列,其中只包含某些值。

如果掩码向右偏移值的索引是奇数,则在置换中包含一个值。在这里考虑二进制,因为一个值应该在排列中或者不是(0或1)并且因为掩码将遍历所有可能的数字,所以所有可能的排列都直接在数字中被表示为二进制:

  • 指数:4,3,2,1,0
  • mask:0 0 0 0 1(抓取索引0,[1])
  • mask:0 0 0 1 0(抓取索引1,[3])
  • mask:0 0 0 1 1(抓取索引0和1,[1,3])
  • mask:1 1 0 0 0(抓取索引3和4,[10,-1])

var a = [1,3,6,10,-1];
function combinations(array, n) {
  var mask, len = array.length, count = 1 << len, permutations = [];
  var indexVisible = function(v, i) { return ((mask >> i) & 1) == 1 }
  var sum = function(a, b) { return a + b }
  for (mask = 1; mask < count; ++mask) {
    permutations.push(array.filter(indexVisible))
  }
  return permutations.filter(function(p) { return p.reduce(sum) <= n })
}
console.log(JSON.stringify(combinations(a, 9)));

函数indexVisible()用于过滤原始数组并返回与掩码匹配的排列。

函数sum()用于将每个排列减少到其值的总和,如果该总和小于或等于n,则它包含在最终结果中并从combinations()


以下是排列: [[1],[3],[1,3],[6],[1,6],[3,6],[1,3,6],[10],[1,10],[3,10],[1,3,10],[6,10],[1,6,10],[3,6,10],[1,3,6,10],[-1],[1,-1],[3,-1],[1,3,-1],[6,-1],[1,6,-1],[3,6,-1],[1,3,6,-1],[10,-1],[1,10,-1],[3,10,-1],[1,3,10,-1],[6,10,-1],[1,6,10,-1],[3,6,10,-1],[1,3,6,10,-1]]

以下是结果: [[1],[3],[1,3],[6],[1,6],[3,6],[-1],[1,-1],[3,-1],[1,3,-1],[6,-1],[1,6,-1],[3,6,-1],[1,3,6,-1],[10,-1]]

您可以在此JSFiddle中查看所有这些内容的工作方式和不同组合。

答案 3 :(得分:2)

修改:给予应有的信用..从this answer借用了大部分逻辑

var combinations = function(a,m) {
  var gc = function(a) {
    var fn = function(n, src, got, all) {
      if (n == 0) {
        if (got.length > 0) {
          all[all.length] = got;
        }
        return;
      }
      for (var j = 0; j < src.length; j++) {
        fn(n - 1, src.slice(j + 1), got.concat([src[j]]), all);
      }
      return;
    }
    var all = [];
    for (var i = 0; i < a.length; i++) {
      fn(i, a, [], all);
    }
    all.push(a);
    return all;
  }
  var c = gc(a);
  return c.filter(function(e) {
    var n = e.length;
    var sum = 0;
    while(n--)
      sum += parseFloat(e[n]) || 0;
    return sum<=m;
  },m);
}
var a = [1,3,6,10,-1];
combinations(a,9);

<强>输出

[[1], [3], [6], [-1], [1, 3], [1, 6], [1, -1], [3, 6], [3, -1], [6, -1], [10, -1], [1, 3, -1], [1, 6, -1], [3, 6, -1], [1, 3, 6, -1]]

答案 4 :(得分:2)

看起来很有趣,不玩,这就是我所拥有的。

的Javascript

function kCombs(set, k) {
    var setLength = set.length,
        combs = [],
        i = 0,
        tailLength,
        head,
        tail,
        j,
        t,
        u;

    if (k > 0 && k <= setLength) {
        if (k === setLength) {
            combs.push(set);
        } else if (k === 1) {
            while (i < setLength) {
                combs.push([set[i]]);
                i += 1;
            }
        } else {
            u = k - 1;
            setLength = setLength - k + 1;
            while (i < setLength) {
                t = i + 1;
                head = set.slice(i, t);
                tail = kCombs(set.slice(t), u);
                j = 0;
                tailLength = tail.length;
                while (j < tailLength) {
                    combs.push(head.concat(tail[j]));
                    j += 1;
                }

                i = t;
            }
        }
    }

    return combs;
}

function combinations(array, n) {
    var arrayLength = array.length,
        combs = [],
        combsLength,
        results = [],
        temp = 0,
        current,
        currentLength,
        i,
        j,
        k = 1;

    while (k <= arrayLength) {
        i = 0;
        current = kCombs(array, k);
        currentLength = current.length;
        while (i < currentLength) {
            combs.push(current[i]);
            i += 1;
        }

        k += 1;
    }

    i = 0;
    combsLength = combs.length;
    while (i < combsLength) {
        j = 0;
        current = combs[i];
        currentLength = current.length;
        while (j < currentLength) {
            temp += current[j];
            j += 1;
        }

        if (temp <= n) {
            results.push(current);
        }

        temp = 0;
        i += 1;
    }

    return results;
}

var a = [1, 3, 6, 10, -1];

console.log(JSON.stringify(combinations(a, 9)));

输出

[[1],[3],[6],[-1],[1,3],[1,6],[1,-1],[3,6],[3,-1],[6,-1],[10,-1],[1,3,-1],[1,6,-1],[3,6,-1],[1,3,6,-1]] 

jsFiddle

所有这些jsPerf,虽然@jcarpenter解决方案含糊不清。

在现代浏览器上,您可以使用for intead while从这个解决方案中挤出更多,因为它们已针对for进行了高度优化。按索引分配而不是push也可以提高性能。

将性能测试扩展到包含更多测试集会很好,如果我觉得无聊的话。

答案 5 :(得分:2)

以下代码将为您提供总计最多9个或更少的所有子数组。

function getSubArrays(arr,n){
  var len = arr.length,
     subs = Array(Math.pow(2,len)).fill();
  return subs.map((_,i) => { var j = -1,
                                 k = i,
                               res = [];
                             while (++j < len ) {
                               k & 1 && res.push(arr[j]);
                               k = k >> 1;
                             }
                             return res;
                           }).slice(1)
                             .filter(a => a.reduce((p,c) => p+c) <= n);
}

var arr = [1,3,6,10,-1],
 result = getSubArrays(arr,9);
console.log(JSON.stringify(result));

答案 6 :(得分:1)

这里的简洁非常神秘。一些描述性功能怎么样?

该方法使用二进制来创建所有可能组合的地图。然后地图用于从数组中提取项目。把弹出的物品加起来,就是这个。

combinations([1, 3, 6, 10, -1], 9)生成的结果是:[[-1],[10,-1],[6],[6,-1],[3],[3,-1],[3,6],[3,6,-1],[1],[1,-1],[1,6],[1,6,-1],[1,3],[1,3,-1],[1,3,6,-1]]

Here is a Fiddle.

/**
* Get an array of all the possible combinations
* of x items.  Combinations are represented as binary.
* @param {Number} x - example 2
* @return {String[]} - example ['00', '01', '10', '11']
*/
function getCombinationsOfXItems(x) {

  var allOn = '',
    numCombos = 0,
    i = 0,
    combos = [];

  // find upper limit
  while (allOn.length < x) {
    allOn += 1;
  }

  // number of possible combinations
  numCombos = parseInt(allOn, 2) + 1;

  // generate the combos
  while(i < numCombos) {
    combos.push(pad(toBase2(i++), allOn.length));
  }

  return combos;
}

/**
* Pad a string with leading zeros.
* @param {String} x - example '100'
* @param {Number} length - example 6
* @return {String} - example '000100'
*/
function pad(x, length) {
  while (x.length < length) {
    x = 0 + x;
  }

  return x;
}

/**
* Get a number as a binary string.
* @param {Number} x - example 3
* @return {String} - example '11'
*/
function toBase2(x) {
  return x.toString(2);
}

/**
* Given an array and a map of its items as a binary string,
* return the items identified by 1.
* @param {Array} arr - example [1,2,3]
* @param {String} binary - example '101'
* @return {Array} - example [1,3]
*/
function pluckFromArrayByBinary(arr, binary) {
  var  plucked = [],
    i = 0,
    max = binary.length;

  for (; i < max; i++) {
    if (binary[i] === '1') {
      plucked.push(arr[i]);
    } 
  }

  return plucked;
}

/**
* Given an array, return a multi-dimensional
* array of all the combinations of its items.
* @param {Array} - example [1, 2];
* @return {Array[]} - [ [1], [1, 2], [2] ]
*/
function getCombosOfArrayItems(arr) {
  var comboMaps = getCombinationsOfXItems(arr.length),
    combos = [];

  // remove the "all off" combo (ex: '00000')
  comboMaps.shift();

  for (var i = 0; i < comboMaps.length; i++) {
    combos.push(pluckFromArrayByBinary(arr, comboMaps[i]));
  }

  return combos;
}

/**
* Return all possible combinations of numbers in an
* array whose sum is less than or equal to n
* @param {Number[]} arr
* @param {Number} x
* return {Number[]} - stringified for readability
*/
function combinations(arr, x) {
  var combos = getCombosOfArrayItems(arr),
    i = 0,
    max = combos.length,
    combo;

  for (; i < max; i++) {
    if (sumArray(combos[i]) > x) {
      combos.splice(i, 1);
      i--;
      max--;
    }
  }

  return JSON.stringify(combos);
}

/**
* Return the sum of an array of numbers.
* @param {Number[]} arr
* @return {Number}
*/
function sumArray(arr) {
  var sum = 0,
    i = 0,
    max = arr.length;

  for (; i < max; i++) {
    sum += arr[i];
  }

  return sum;
}

console.log(combinations([1, 3, 6, 10, -1], 9));

答案 7 :(得分:1)

@jcarpenter解决方案非常好我只需要为喜欢ECMA5的人重做它。这不会像for的原始力量那么快,现代方法没有那么长的时间进行如此高度优化(并且他们做了相当多的工作)。但是性能结果确实显示了powerSet算法有多好(并且它是一个可重用的函数)。我也过滤掉了歧义,这会让事情变得缓慢。

的Javascript

function powerSet(arr) {
    var lastElement,
        val;

    if (!arr.length) {
        val = [[]];
    } else {
        lastElement = arr.pop();
        val = powerSet(arr).reduce(function (previous, element) {
            previous.push(element);
            element = element.slice();
            element.push(lastElement);
            previous.push(element);

            return previous;
        }, []);
    }

    return val;
}

function combinations(array, n) {
    return powerSet(array).filter(function (set) {
        return set.length && set.reduce(function (previous, element) {
            return previous + element;
        }, 0) <= n;
    });
}

var a = [1, 3, 6, 10, -1];

console.log(JSON.stringify(combinations(a, 9)));

输出

[[-1],[10,-1],[6],[6,-1],[3],[3,-1],[3,6],[3,6,-1],[1],[1,-1],[1,6],[1,6,-1],[1,3],[1,3,-1],[1,3,6,-1]] 

jsFiddle

并添加到jsPerf

答案 8 :(得分:0)

试试这个:

var a = [1,3,6,10,-1];
function combinations(array, n) {
    var arrayCopy = [],
        results = [];

    // duplicate the array
    for (var i in array)
        arrayCopy[i] = array[i];

    for (var i in array)
        for (var j in arrayCopy)
            if ((array[i] + arrayCopy[j]) <= n)
                results.push([array[i], arrayCopy[j]]);

    return results;
}

console.log(combinations(a, 9));

记录:

[1, 1], [1, 3], [1, 6], [1, -1], 
[3, 1], [3, 3], [3, 6], [3, -1], 
[6, 1], [6, 3], [6, -1], 
[10, -1], 
[-1, 1], [-1, 3], [-1, 6], [-1, 10], [-1, -1]