避免重复计算以优化嵌套循环的时间复杂性

时间:2018-11-27 08:30:23

标签: javascript algorithm loops optimization big-o

今天,我使用下面的代码在HackerRank上进行了一个简单的挑战,该代码100%可以接受并且可以正常工作,但是我想知道是否有办法通过消除重复计算来进一步减少所需的循环。

让我直观地向您展示正在发生的事情,当我完成操作时,我的代码示例将变得非常糟糕!

该代码获取数字数组中的第一个数字,并将其添加到每个后续数字中,并检查其是否可以被k = 3整除。

在一个由6个数字组成的数组中,等于15个循环,即O(n²),这意味着我的循环将成倍地增长到输入量。 7个数字将是21个循环。

PS,您可能会认为6个应该是21个循环,而7个应该是28个循环,但是请记住,除了最后一个数字,我总是将当前数字添加到下一个循环中。

视觉细分

input: [1, 3, 2, 6, 1, 2]

  • 1 + 3, 1 + 2 ,1 + 6、1 + 1, 1 + 2
  • 3 + 2, 3 + 6 3 + 1 3 + 2
  • 2 + 6, 2 + 1 ,2 + 2
  • 6 + 1 6 + 2
  • 1 + 2

说明

如果您查看我在粗体中输入的数字,则会发现它们是重复的计算。 斜体数字是可以用k = 3整除的数字。现在我们要解决我的问题了。如何消除重复的数学运算,在这个特定示例中,这会使我的循环次数从15下降到8。如果所有数字都不相同,该算法在O(n²)的情况下仍然会更糟,但这仍然是一种优化。

代码演示

function divisibleSumPairs(k, a) {
  let pairs = 0;
  for (let i = 0; i < a.length - 1; i++) {
    for (let j = i + 1; j < a.length; j++) {
      if ((a[i] + a[j])/k % 1 === 0) pairs++;
    }
  }
  console.log(pairs);
}

divisibleSumPairs(3, [ 1, 3, 2, 6, 1, 2 ])

2 个答案:

答案 0 :(得分:3)

我花了一段时间思考如何对数字数组进行预处理以防止重复计算,然后我退了一步,回到头脑清醒,喝冷水的问题。

然后我想“如果要预处理除数怎么办?”

这种方法的缺点是它创建和数组大小等于除数的数组,但是这样做的时间复杂度为HitID = vars.get("AddPrpc139"); b=139 if(HitID.equals(b)) { log.info("......value="); }else { log.info("......value="); } (螺杆空间复杂度,哈哈)

在此特定示例中,我们有3个循环用于除数,6个循环用于计算,总共9个循环,与原始解决方案相比节省了6个循环,并且消除了O(n)

这导致我的函数的整体时间复杂度为O(n²)

O(n)

性能测试

我通过性能测试运行了几次代码迭代,很惊讶地发现,简单的function divisibleSumPairs(k, a) { const mod = new Array(k).fill(0); let pairs = 0; for (let i = 0; i < a.length; i++) { const position = a[i] % k; pairs += mod[(k - position) % k]; mod[position]++; } console.log(pairs); } divisibleSumPairs(3, [ 1, 3, 2, 6, 1, 2 ])循环比forforEach好得多。

  1. reduce:原始代码
  2. for^2:本文中的代码
  3. for:这篇文章,改用forEach
  4. forEach:这篇文章,改用reduce

https://jsperf.com/for-2-vs-for-vs-foreach-vs-reduce/1

答案 1 :(得分:0)

要解决此动态问题

如果发现结果,请尝试将结果存储在Object中,假设sum_map,这意味着如果不计算总和,我们已经计算了该总和,并将结果存储在地图中以供将来参考。

示例代码段

let d = [1, 3, 2, 6, 1, 2]

const len = d.length

const sum_map = {}

let pairs = 0

for (let i = 0; i < d.length - 1; i++) {
  for (let j = i + 1; j < d.length; j++) {
    const key1 = `${d[i]}_${d[j]}`
    const key2 = `${d[j]}_${d[i]}`
    let result = 0
    if (sum_map[key1]) {
      result = sum_map[key1]
    } else if (sum_map[key2]) {
      result = sum_map[key2]
    } else {
      result = d[j] + d[i]
      sum_map[`${d[i]}_${d[j]}`] = result
    }
    if (result % 3 === 0) {
      pairs += 1
    }
  }
}
console.log(pairs)

为了避免O(n ^ 2),一个简单的技巧就是知道

示例

说您要检查的数字是5且arr = [1,3,2,6,1,2,5]

  1. 只有在存在数字补余数的情况下,您才会发现被数字整除的和。

像被5整除的数字对一样,它们只是给定余数的余数,即3 % 5 = 22 % 5 = 3,所以总和将被5整除

要解决此问题,只需找到剩余的称赞并从中选择

就像说,您是3位数字给出2的余数,而4位数字给出3位的余数

因此,配对将从这3个数字中选择1个*从这4个数字中选择1个

  1. 如果数字可被5整除,但如果数字只能被1整除,则其总和将永远不可被整除。

代码段:

let d = [1, 3, 2, 6, 1, 2, 5]

const check_div_num = 5

remainder_map = {}

mod_arr = d.map((i) =>{
  const rem = i % 5
  if(remainder_map[rem]) {
    remainder_map[rem] += 1
  } else {
    remainder_map[rem] = 1
  }
  return rem
})

const till = Math.floor(check_div_num / 2)

keys = Object.keys(remainder_map)


let pairs = 0

for (let j = 0; j < keys.length; j++) {
  const key = keys[j]
  if(key === '0' && remainder_map["0"] > 1) {
    pairs += remainder_map[key] / 2
    continue
  }
  if(Number(key) <= till) {
    let compliment = remainder_map[check_div_num - Number(key)]
    const compliemnt_key = check_div_num - Number(key)
    if(compliment) {
      pairs += remainder_map[key]*remainder_map[compliemnt_key.toString()]
    } else {
      continue
    }
  } else {
    break
  }
}
console.log(pairs)

请注意,我只循环到5的一半,即Math.floor(5/2),因为我们已经在检查他们的称赞了