用于跟踪HackerRank问题的算法/代码的改进

时间:2019-02-11 11:04:53

标签: javascript algorithm

我知道,SO不是作业的场所,因此,它非常针对问题范围。

我正在尝试在HackerRank:Array Manipulation - Crush上解决此问题。问题陈述非常简单,我实现了以下代码:

function arrayManipulation(n, queries) {
  const arr = new Array(n).fill(0)
  for (let j = 0; j < queries.length; j++) {
    const query = queries[j];
    const i = query[0] - 1;
    const limit = query[1];
    const value = query[2];
    while (i < limit) {
      arr[i++] += value;
    }
  }
  return Math.max.apply(null, arr);
}

现在,它可以在一半的测试用例中正常工作,但会中断以下消息:由于超时7到13个测试用例,由于超时而终止,因为时间限制为 1秒 em>。

所以问题是,我可以在哪些方面改进此代码。根据我的理解,使用当前的算法并没有太多的范围(我可能是错的),那么如何改进算法呢?


注意:不使用.map之类的数组函数来寻找替代项,因为.reduce更快。另外,使用for可以更快地进行自定义循环。为其附加参考。

参考:

4 个答案:

答案 0 :(得分:2)

我们可以对此问题进行一些观察

  • 当我们从数组的开头到结尾进行迭代时,让我们保持一个代表当前值的运行总和。
  • 如果我们将每个操作分解为另外两个操作(abk)->(ak)和(b -k),且(ak)表示将k添加到位置a和(b -k)的运行总和中表示从位置b处的总和中减去k。
  • 我们可以首先按位置对所有这些操作进行排序,然后对它们的运算符(加减运算符)进行排序,这样我们始终可以获得正确的结果。

时间复杂度O(q log q)与q是查询量。

示例:

a b k
1 5 3
4 8 7
6 9 1

我们将其分为

(1 3) (5 -3) (4 7) (8 -7) (6 1) (9 -1)

对它们进行排序:

(1 3) (4 7) (5 -3) (6 1) (8 -7) (9 -1)

然后一个接一个地进行:

Start sum = 0 
-> (1 3)  -> sum = 3
-> (4 7)  -> sum = 10
-> (5 -3) -> sum = 7
-> (6 1)  -> sum = 8
-> (8 -7) -> sum = 1
-> (9 -1) -> sum = 0

最大和为10->问题的答案。

我的Java代码通过了所有测试https://ideone.com/jNbKHa

答案 1 :(得分:1)

此算法会有所帮助。

https://www.geeksforgeeks.org/difference-array-range-update-query-o1/

使用此算法,您可以解决O(n+q)n = size of the arrayq = no of queries的问题。

答案 2 :(得分:1)

为什么您的暴力解决方案无法通过所有测试用例?

今天的生成系统可以在一秒钟内执行10 ^ 8操作。请记住,在最坏的情况下,每个查询必须处理N = 10 ^ 7个输入。当您使用两个嵌套的for循环(一个用于添加K元素,另一个用于处理m个查询)时,解决方案的复杂度为O(NM)。

如果您使用O(NM)复杂性的解决方案,则必须处理(10 ^ 7 * 10 ^ 5)= 10 ^ 12的操作(在更坏的情况下(根本无法在1秒内计算出))

这就是为什么您会因蛮力解决方案而遇到超时错误的原因。 因此,您需要优化代码,这可以借助前缀求和数组来完成。

不是将k加到数组中从a到b的范围内的所有元素上,而是将差值数组累加

每当我们在数组的任何索引处添加任何内容并应用前缀求和算法时,都会将同一元素添加到每个元素,直到数组结尾。

ex- n = 5,m = 1,a = 2 b = 5 k = 5

    i     0.....1.....2.....3.....4.....5.....6   //take array of size N+2 to avoid index out of bound
  A[i]    0     0     0     0     0     0     0

将k = 5添加到a = 2

A [a] = A [a] + k //从应该添加k元素的位置开始索引

     i    0.....1.....2.....3.....4.....5.....6 
   A[i]   0     0     5     0     0     0     0

现在应用前缀和算法

     i    0.....1.....2.....3.....4.....5.....6 
  A[i]    0     0     5     5     5     5     5

因此您可以看到K = 5在应用前缀sum之后添加到所有元素,直到末尾,但是我们不必在末尾添加k。因此,要取消此效果,我们还必须在b + 1索引之后添加-K,以便仅在[a,b]范围内才会有K个元素的添加效果。

A [b + 1] = A [b] -k //删除第b个索引之后先前添加的k元素的影响。 这就是为什么在初始数组中将-k与+ k一起添加。

    i    0.....1.....2.....3.....4.....5.....6 
  A[i]   0     0     5     0     0     0    -5

现在应用前缀和数组

    i    0.....1.....2.....3.....4.....5.....6 
  A[i]   0     0     5     5     5     5     0

您现在可以看到,将K = 5从a = 2添加到b = 5,这是预期的。 在这里,我们只为每个查询更新两个索引,因此复杂度将为O(1)。

现在在输入中应用相同的算法

         # 0.....1.....2.....3.....4.....5.....6    //taken array of size N+2 to avoid index out of bound
5 3      # 0     0     0     0     0     0     0
1 2 100  # 0    100    0   -100    0     0     0       
2 5 100  # 0    100   100  -100    0     0   -100
3 4 100  # 0    100   100    0     0  -100   -100

要计算最大前缀和,请在获取最大累积前缀的同时将差分数组累加到。

执行所有操作后,现在应用前缀求和数组

    i      0.....1.....2.....3.....4.....5.....6 
  A[i]     0     100   200  200   200   100    0

现在您可以遍历此数组以找到最大为200的数组。 遍历数组将花费O(N)时间,而更新每个查询的两个索引将花费O(1)*查询数量(m)

总体复杂度= O(N)+ O(M)                   = O(N + M)

这意味着=(10 ^ 7 + 10 ^ 5)小于10 ^ 8(每秒)

注意:如果要搜索 video tutorial ,则必须将其签出 here 以获得详细说明。< / p>

答案 3 :(得分:0)

我认为诀窍不是真正对数组执行操作。

您可以简单地跟踪索引间隔的变化。

保留间隔的排序列表(按begin-index排序)。

$ mv $HOME/.docker to_something_else

在最后一步中,您要遍历间隔并取最大值。