【问题标题】:Logic used behind Array Manipulation of HackerRankHackerRank 的数组操作背后使用的逻辑
【发布时间】:2018-01-09 06:00:52
【问题描述】:

我无法理解Hackerrank这个问题的解决逻辑,https://www.hackerrank.com/challenges/crush/problem

在讨论部分,许多人也发布了他们的解决方案,但我无法理解为什么这种逻辑有效。

以下解决方案取自同一问题的讨论部分,并且拥有最多的赞成票,

#include <cmath>
#include <cstdio>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;


int main() {
    long int N,K,p,q,sum,i,j,max=0,x=0;

    cin>>N>>K;
    long int *a=new long int[N+1]();

    for(i=0;i<K;i++)
    {
        cin>>p>>q>>sum;
        a[p]+=sum;
        if((q+1)<=N) a[q+1]-=sum;
    }

    for(i=1;i<=N;i++)
    {
       x=x+a[i];
       if(max<x) max=x;

    }

    cout<<max;
    return 0;
}

有人可以解释一下背后的逻辑吗? 非常感谢您的帮助。

【问题讨论】:

  • 问题是“为什么评价这么高?”。我们可以简单地猜测,评估者不会对有问题的代码(例如using namespace std;)给予任何惩罚。

标签: c++ arrays


【解决方案1】:

我们基本上将增量存储在起始位置和范围中的最后一个索引之后。对于a b k,我们将为索引[a,b] 中的所有元素增加+k,但不会增加下一个元素。所以我们要减去它,因为 w.r.t 之前的增量范围右侧的所有元素将比-k 小。我们基本上是通过这个递增/递减来存储所有的最终值。

最后,我们从左到右动态计算元素。 如果你想得更深入一点,它只是存储一个元素比前一个元素大多少。

最初的数组是0 0 0 0 0

在第一次操作1 3 3 之后,数组元素应该是 3 3 3 0 0 但我们是这样存储的

3 0 0 -3 0

意义

First element is 3 greater than 0.
Second  ->       0 greater than index 1 element.
Third   ->       0 greater than index 2 element
Fourth  ->      -3 greater than index 3 element.
fifth   ->       0 greater than index 4 element.

在第二次操作2 4 4 之后,数组原本是3 7 7 4 0,但我们将它存储为3 4 0 -3 -4。就像我详细描述的那样,记住这一点并以这种方式思考,你会发现我们并没有丢失信息。我们只是以不同的方式存储它。

最终值将是

0+(3) 0+3+(4) 0+3+4+(0) 0+3+4+0+(-3) 0+3+4+0-3+(-4)

3  7    7       4           0  matches with what we got earlier.

注意我们如何计算每个元素。只需将前一个元素与当前元素更大的值相加。


请注意,此解决方案有效,因为它只被查询一次。如果它被查询m 次,那么这个解决方案不起作用,因为它会超时。然后,您将不得不使用高级数据结构(如段树和/或二叉索引树)进行更深入的挖掘。

【讨论】:

    【解决方案2】:

    这两个地方帮助我更清楚地理解了这个算法。 Prefix_sum_array_and_difference_array
    Stack Overflow

    如果你想要一个简单直接的解释: 初始,数组为 0 0 0 0 0 cpp after the first operation, 1 2 100 it will become seq1: 100 100 0 0 0 and after second 2 5 100 seq2: 0 100 100 100 100 and after 3 4 100 seq2: 0 0 100 100 0 但是当我们应用差异数组时 每一步,我们都会得到

    cpp diff seq of seq1: 100 0 -100 0 0 diff seq of seq2: 0 100 0 0 0 -100 diff seq of seq3: 0 0 100 0 -100

    一个重要的性质是序列和的差序列是差序列的和。

    它会给我们, cpp 100 100 0 0 -100 -100(for clarity purpose only) 或者您可以将所有序列添加为 cpp seq1+seq2+seq3 = 100 200 200 200 100 然后找到差异seq或差异数组100 100 0 0 -100然后找到前缀数组。

    为什么我们忽略前 100 个??? 阅读第一篇关于差分数组和前缀和数组的文章!!!

    然后,做前缀求和 cpp 100 200 200 200 100 0 忽略最后一个 0,因为我们考虑的最后一个索引只是为了清楚起见。

    所以,这两个步骤都为我们找到了差异数组:) cpp a[p]+=sum; if((q+1)<=N) a[q+1]-=sum;

    【讨论】:

      【解决方案3】:

      我将尝试解释我对此的理解:
      每行输入基本上描述了一个序列,并要求您找到这些序列之和的最大值。
      例如,如果N 被指定为5:
      2 4 13 行描述了序列 [0, 13, 13, 13, 0]
      3 5 11 行描述了序列 [0, 0, 11, 11, 11]
      如果这些是唯一的行,我们从两者的逐点和得到结果序列,即[0, 13, 24, 24, 11]

      现在我们可以通过差序列来描述上述序列的另一种方式,即在索引i处,我们将保留索引i处的元素与索引i-1处的元素之间的差异,并且我们可以通过差序列的运行和得到原始序列。

      在上述序列的情况下,差异序列为:
      [0, 13, 0, 0, -13]2 3 13描述的序列
      [0, 0, 11, 0, 0]3 5 11描述的序列
      [0, 13, 11, 0, -13为序列的总和。

      一个重要的性质是序列和的差序列是差序列的和

      因此,对于每一行,解决方案的作用是对差异序列求和(由于序列的性质,最多只需要 2 次操作),然后找到差异序列的运行总和的最大值,从而得到序列的元素,并保持该运行总数的最大值。

      虽然我给出的示例只有 2 行,但同样的想法适用于任意数量的行。

      我希望这可以很好地了解解决方案背后的想法。

      【讨论】:

      【解决方案4】:

      不是将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 // 类似于 a[p]+=sum;

           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 在应用前缀总和后添加到所有元素直到最后,但我们不必添加 k 直到最后。所以要消除这种影响,我们必须在 b+1 索引之后添加 -K ,这样只有 [a,b] 范围内才会有 K 元素添加效果。

      A[b+1]=A[b]-k // 去除之前在第b个索引之后添加的k个元素的效果。(同a[q+1]-=sum;) 这就是为什么在初始数组中添加 -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了解详细说明。

      【讨论】:

        【解决方案5】:

        蛮力方法要求我们遍历数组并将值添加到 a 到 b 范围内的每个元素。

        我们可以做的不是每次都循环,而是在索引 a 处添加值并从索引 b+1 中减去它。最后我们像前缀数组一样从左到右添加。

        所以本质上发生的是我们添加的值从 a 反映到 b。而且当我们从 b+1 中减去相同的值时,它不会在索引 b 后反映。

        而且这个算法的运行时间为 O(n+m)。

        如果您对解释有任何疑问,可以观看此视频,该视频很好地解释了算法,并通过在几个测试用例上干运行算法来帮助更好地理解。只需 7 分钟的视频 :)。

        链接:https://youtu.be/JtJKn_c9lB4

        【讨论】:

          【解决方案6】:

          以下代码在 C++ 中对我有用。我在网上找了一些帮助,然后编写了代码。

          long arrayManipulation(int n, vector<vector<int>> queries) {
            vector<long> resultVector(n);
            long maxVal=0, x=0, i;
          
            for(int i = 0; i<n ; i++)
            {
                resultVector[i]=0;
            }
          
            for(i=0; i<queries.size(); i++)
            {
                resultVector[(queries[i][0])-1] += queries[i][2];
          
                if((queries[i][1]) <= n)
                {
                    resultVector[(queries[i][1])] -= queries[i][2];
                }
            }
          
            for(i=0; i <n; i++)
            {
                x+=resultVector[i];
                if(x>maxVal)
                {
                    maxVal=x;
                }
            }
          
            return maxVal;
          }
          

          【讨论】:

          • 欢迎堆栈溢出!该问题与理解所提供的解决方案特别相关,因此如果您想帮助您可以在上面对您的解决方案进行注释以提供更多信息,或提供有关其工作原理的一些详细信息。
          • 为避免分段错误错误,您必须为vector&lt;long&gt; resultVector(n+1) 再分配一个元素,因为您在设置queries[i][1]) &lt;= n) 时的值
          【解决方案7】:
          Before solving this problem you must know Prefix Sum Array & Difference array. 
          
          consider below case
          a b k
          1 5 3
          4 8 7
          6 9 1 
          
          # if we calculate original array 'A' from this it will be
          [3,3,3,10,10,8,8,8,1,0]
          
          # now, lets find out the difference array 'D(A)'
          [3,0,0,7,0,-2,0,0,-7,-1]
          
          # follow below steps & calculate the array
          A[a] += k
          A[b+1] -= k , b+1 < len(A)
          you will get [3,0,0,7,0,-2,0,0,-7,-1] which is D(A) itself.
          
          # P(0, D(A)) = A. i.e. calculate prefix sum array of D(A). you will get the original array.
          [3,3,3,10,10,8,8,8,1,0]
          
          return max :)
          

          【讨论】:

            【解决方案8】:

            它使用了数组差异的概念。将值添加到给定范围(i,j,k)是一个新概念。 i 和 j 指定范围,k 是要添加的值。 如果您检查链接,这将非常有帮助。 https://www.geeksforgeeks.org/difference-array-range-update-query-o1

            【讨论】:

              猜你喜欢
              • 2021-12-30
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2013-11-04
              • 1970-01-01
              • 2019-10-08
              • 1970-01-01
              相关资源
              最近更新 更多