【问题标题】:Improve performance of this combination algorithm?提高这种组合算法的性能?
【发布时间】:2018-08-07 10:33:17
【问题描述】:

我正在处理来自 Codewars 的 this kata。任务是:

给定一个数,用它的位数可以得到多少个三的倍数?

假设你有数字 362。可以从中生成的数字是:

362 ----> 3, 6, 2, 36, 63, 62, 26, 32, 23, 236, 263, 326, 362, 623, 632

我编写了以下递归函数来计算所有可能性:

const findMult_3 = (num) => {

  const powerset = (set) => {
    const combinations = []
    const combine = (prefix, chars) => {
      for (let i = 0; i < chars.length; i++) {
        const newPrefix = parseInt(prefix + chars[i])
        if (!combinations.includes(newPrefix)) {
          combinations.push(newPrefix)
        } else {
          console.log('encountered duplicate')
        }
        combine(newPrefix, chars.filter((x, ind) => ind !== i))
      }
    }
    combine('', set)
    return combinations.sort((a, b) => a - b)
  }

  const allCombinations = powerset(num.toString().split(''))
  const factorsOfThree = allCombinations.filter(x => x % 3 === 0).filter(x => x !== 0)

  return [factorsOfThree.length, factorsOfThree.pop()]

}

findMult_3(43522283000229)

我很早就注意到我遇到了很多重复案例,因此使用了console.log('encountered duplicate') 标志。

这个算法的执行对于大数字来说需要很长时间,例如43522283000229

我怎样才能提高这段代码的性能,还是应该完全废弃它?

【问题讨论】:

标签: javascript algorithm performance combinations permutation


【解决方案1】:

对于大多数编码模式,算法的选择远比实现细节更重要,但在我们开始之前,让我指出你的实现中最明显的缺陷:

    if (!combinations.includes(newPrefix)) {
      combinations.push(newPrefix)
    } else {
      console.log('encountered duplicate')
    }
    combine(newPrefix, chars.filter((x, ind) => ind !== i))

combinations 是一个数组,includes 通过遍历数组并检查每个元素来工作。也就是说,要检查一个元素是否重复,您需要将它与之前遇到的每个组合进行比较。由于其中的数量呈指数级增长,因此这将非常缓慢。如果您改用字典对象或Map,您的代码会快得多。

另外,您是否注意到即使组合是重复的,您也在继续生成组合?这是多余的。

所以廉价的改进是:

const combinations = {};
if (combinations[prefix]) {
  // duplicate, do nothing
} else {
  combinations[prefix] = true;
  combine(...);
}

然而,真正的改进是选择更好的算法。如果您利用问题的数学结构,您可能能够找到解决方案的数量,而无需遍历所有解决方案。

以下见解可能会有所帮助:

  • 一个数能被三整除当且仅当它的数字之和是。
  • 数字之和可以被 3 整除当且仅当它们除以 3 时的余数之和为 0。
  • 输入中的数字顺序无关紧要

【讨论】:

  • 好的,谢谢。我不知道includes 的性能如此糟糕,因为我只在相对较小的数组中使用过它。改为使用普通对象后,性能大大提高。您从一开始就选择更好的算法的观点也得到了适当的注意。
【解决方案2】:

一个(第一个)优化是只检查或生成 数字之和可被 3 整除的数字,因为只有这些数字可以被 3 整除。

因此,在您的示例 (362) 中,您可以跳过与 3 and 26 and 2 的所有组合以及与 3 位数字的所有可能组合(因为 3 位数字的总和不能被 3 整除)。

对于较大的数字 (43522283000229),您可以跳过更多内容,例如所有数字组合:

43, 35, 52, ...
435, 352, .., 283, ...
4352 (thus, including all possible combinations of those 4 digits), ...
43522, ...
43528, 43529, ...
43528309, ...
and so on

可能的算法

Original number:        43522283000229

First, sort the digits: 00022222334589

Then find all distinct combinations of digits where
their sum is a multiple of 3:

left to right:

1 digit : 3, 9
2 digits: 03, 09, 24, 33, 39, 45, 48, ...
3 digits: 003, 009, 024, 033, 039, 222, 234, ...
n digits ...

Now, for all above numbers create every possible combination with their
digits, skip those with leading zeros.:

3, 9, 30, 90, 24, 42, 33, 39, 93, 45, 54, 48, 84, 300, 900,
204, 240, 402, 420, 309, 390, 903, 930, 222, 234, 243, ...

We don't have to check for division by 3, they all match.
We don't have to check for duplicates.

You could then sort the resulting list if needed.

【讨论】:

    猜你喜欢
    • 2021-12-06
    • 2011-02-28
    • 2021-04-12
    • 2014-06-14
    • 1970-01-01
    • 2015-02-19
    • 1970-01-01
    • 2020-01-19
    • 1970-01-01
    相关资源
    最近更新 更多