【问题标题】:Avoid duplicate calculations to optimize time-complexity of nested for loop避免重复计算以优化嵌套 for 循环的时间复杂度
【发布时间】:2019-04-28 22:45:39
【问题描述】:

今天我用下面的代码在 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+16+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 ])

【问题讨论】:

  • SO 用于特定的编程问题。您的问题与CR 相比,更具有主题性。
  • 恭喜!你刚刚揭开了dynamic programming的奇妙世界!现在去领取你的CLRS副本。
  • @hindmost,感谢您对 CR 的提醒。我对在哪个地方发布问题有点犹豫。
  • 谢谢@EmilVikström,我实际上已经拥有那本书了。 :)
  • 如果您需要处理所有组合,则无法减少循环数。但是你想要达到的目标是什么?你需要得到除以3的总和吗?或数字的位置他们的总和% 3 === 0?还是什么?

标签: javascript algorithm loops optimization big-o


【解决方案1】:

实现这个动态问题

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

样本sn-p:

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 nums 给出余数 2 和 4 nums 给出余数 3

所以对将从这 3 个数字中选择 1 * 从这 4 个数字中选择 1

  1. 如果数字可以被 5 整除,但如果它只有 1,那么它的总和将永远不能被整除。

代码sn-p:

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),因为我们已经在检查他们的赞美

【讨论】:

  • 我正在考虑做同样的事情(在地图中跟踪以前的计算),但它仍然是相同数量的循环。我认为需要对原始数组进行一些预处理以减少循环数量。
  • 我发布了这种方法,因为您的问题标题说避免重复计算,即使您预处理数组以检查其余项目,它将是 O(n^2)
  • 感谢您的回答。问题是这样做是为了优化时间复杂度,即减少循环。
  • 我已经更新了基于恭维方法查找配对的答案
  • 我怀疑sum_map 的帮助 - 字符串连接和对象查找比简单的添加要昂贵得多。不过,恭维余数方法是一个很好的解决方案。
【解决方案2】:

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

然后我想“如果我改为预处理除数会怎么样”?

这种方法的缺点是它创建和除数大小相等的数组,但它在 O(n) 时间复杂度(螺丝空间复杂度,lol)中完成它

对于这个特定的示例,我们有 3 个循环用于除数,6 个循环用于计算,总共 9 个循环,这比原始解决方案节省了 6 个循环,并消除了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 ])

性能测试

我通过性能测试对我的代码进行了多次迭代,我惊讶地发现一个简单的for 循环与forEachreduce 相比要好得多。

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

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

【讨论】:

  • 完美!!我想出了同样的方法! +1
  • pairs += mod[k - position]; 就足够了,因为0 &lt;= position &lt; k
  • k - position 有时会返回高于mod 长度的索引位置,导致NaN 由于undefinedmod 返回而导致mod
猜你喜欢
  • 1970-01-01
  • 2018-05-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-05-18
  • 1970-01-01
相关资源
最近更新 更多