【问题标题】:Efficient algorithm to get the combinations of all items in object获取对象中所有项目组合的高效算法
【发布时间】:2018-01-03 00:21:42
【问题描述】:

给定一个具有 n 个键的数组或对象,我需要找到长度为 x 的所有组合。
鉴于X 是可变的。 binomial_coefficient(n,x).

目前我正在使用这个:

function combine(items) {
    var result = [];
    var f = function(prefix, items) {
        for (var i = 0; i < items.length; i++) {
            result.push(prefix + items[i]);
            f(prefix + items[i], items.slice(i + 1));
        }
    }
    f('', items);
    return result;
}

var combinations = combine(["a", "b", "c", "d"]);

输出是:

["a", "ab", "abc", "abcd", "abd", "ac", "acd", "ad", "b", "bc", "bcd", "bd", "c", "cd", "d"]

所以如果我想要来自n=4 的二项式系数x=3,我会选择所有长度等于3 的字符串。 {abc, abd, acd, bcd}。

所以我分两步完成。

有没有更高效、复杂度更小的算法?

链接: Solution performance (JSPerf)

【问题讨论】:

  • 谢谢大家。我创建了一个 jsperf 测试,其中包含所有答案 here,在不同浏览器和 PC 中测试了几个值之后,我认为 David 有最快的解决方案

标签: javascript algorithm dynamic-programming memoization binomial-coefficients


【解决方案1】:

我们可以只创建我们感兴趣的组合。此外,我们可以使用指向原始数组的指针,而不是在每次调用中使用 slice 来克隆数组。这是一个版本。在没有外部全局变量的情况下将其转换为递归留作练习。

function choose(ns,r){
  var res = [];

  function _choose(i,_res){
    if (_res.length == r){
      res.push(_res);
      return;

    } else if (_res.length + ns.length - i == r){
      _res = _res.concat(ns.slice(i));
      res.push(_res);
      return
    }

    var temp = _res.slice();
    temp.push(ns[i]);

    _choose(i + 1,temp);
    _choose(i + 1,_res);
  }

  _choose(0,[]);
  return res;
}

var combinations = choose(["a", "b", "c", "d"], 3);
console.log(JSON.stringify(combinations));

【讨论】:

    【解决方案2】:

    你的算法几乎是O(2^n),你可以丢弃很多组合,但元素的数量将是(n! * (n-x)!) / x!

    要丢弃无用的组合,您可以使用索引数组。

     function combine(items, numSubItems) {
            var result = [];
            var indexes = new Array(numSubItems);
            for (var i = 0 ; i < numSubItems; i++) {
                indexes[i] = i;
            }
            while (indexes[0] < (items.length - numSubItems + 1)) {
                var v = [];
                for (var i = 0 ; i < numSubItems; i++) {
                    v.push(items[indexes[i]]);
                }
                result.push(v);
                indexes[numSubItems - 1]++;
                var l = numSubItems - 1; // reference always is the last position at beginning
                while ( (indexes[numSubItems - 1] >= items.length) && (indexes[0] < items.length - numSubItems + 1)) {
                    l--; // the last position is reached
                    indexes[l]++;
                    for (var i = l +1 ; i < numSubItems; i++) {
                        indexes[i] = indexes[l] + (i - l);
                    }
                }        
            }
            return result;
        }
    
        var combinations = combine(["a", "b", "c", "d"], 3);
        console.log(JSON.stringify(combinations));

    例如,第一个组合具有索引:[0, 1, 2] 和元素 ["a", "b", "c"]。为了计算下一个组合,它获取最后一个索引2并尝试递增,如果递增低于最大位置(在本例中为4),则到达下一个组合,但如果不是,它必须递增到前一个索引。

    【讨论】:

      【解决方案3】:

      您可以使用迭代和递归方法,强调数组的长度和仍然需要的项目。

      基本上combine() 采用一个数组,其中包含要组合的值和所需组合结果集的大小。

      内部函数c() 将一个先前组合的数组和一个起始值作为原始数组的索引进行组合。返回是一个包含所有组合的数组。

      第一次调用总是c([], 0),因为结果数组为空且起始索引为 0。

      function combine(array, size) {
      
          function c(part, start) {
              var result = [], i, l, p;
              for (i = start, l = array.length; i < l; i++) {
                  p = part.slice(0);                       // get a copy of part
                  p.push(array[i]);                        // add the iterated element to p
                  if (p.length < size) {                   // test if recursion can go on
                      result = result.concat(c(p, i + 1)); // call c again & concat rresult
                  } else {
                      result.push(p);                      // push p to result, stop recursion
                  }
              }
              return result;
          }
      
          return c([], 0);
      }
      
      console.log(combine(["a", "b", "c", "d"], 3));
      .as-console-wrapper { max-height: 100% !important; top: 0; }

      【讨论】:

        【解决方案4】:

        这是真正的递归。

        function seq(a,b){
          var res = [];
          for (var i=a; i<=b; i++)
            res.push(i);
          return res;
        }
        
        function f(n,k){
          if (k === 0)
            return [[]];
            
          if (n === k)
            return [seq(1,n)];
            
          let left = f(n - 1, k - 1),
              right = f(n - 1, k);
            
          for (let i=0; i<left.length; i++)
            left[i].push(n);
          
          return left.concat(right);
        }
        
        console.log(JSON.stringify(f(4,3)))

        【讨论】:

          猜你喜欢
          • 2021-07-30
          • 1970-01-01
          • 2020-12-30
          • 1970-01-01
          • 2017-02-26
          • 2018-09-17
          • 1970-01-01
          • 2023-02-07
          • 1970-01-01
          相关资源
          最近更新 更多