我无法掌握Hackerrank问题解决方案背后的逻辑,https://www.hackerrank.com/challenges/crush/problem
在讨论部分,许多人也发布了他们的解决方案,但我无法理解为什么这种逻辑有效。
以下解决方案取自相同问题的讨论部分,并且具有最大数量的upvotes,
#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;
}
有人可以解释一下背后的逻辑吗? 非常感谢您的帮助。
答案 0 :(得分:25)
我们基本上将增量存储在范围中的起始和最后一个元素之后。对于a b k
,我们将为索引+k
中的所有元素增加[a,b]
,但接下来的元素不会增加。所以我们减去它,因为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
次,那么这个消息不起作用。然后,您必须使用segment tree
或binary indexed tree
深入挖掘。
答案 1 :(得分: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
处的元素与索引{{处的元素之间的差异1}},我们可以通过差分序列的运行总和得到原始序列。
在上述序列的情况下,差异序列是:
i-1
所描述的序列[0, 13, 0, 0, -13]
2 3 13
所描述的序列[0, 0, 11, 0, 0]
3 5 11
表示序列的总和。
一个重要特性是序列总和的差异序列是差异序列的总和。
因此,对于每一行,解决方案的作用是对差异序列求和(由于序列的性质,最多只需要2次操作),然后找出差异序列的运行总和所需的最大值,从而得到序列的元素,并保持该运行总数的最大值。
虽然我给出的示例只有2行,但同样的想法适用于任意数量的行。
我希望这能为解决方案背后的理念提供良好的直觉。
答案 2 :(得分:3)
这两个地方帮助我更清楚地理解了这个算法。
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? 阅读关于差异数组和前缀和数组的第一篇文章!!!!
之后,做前缀sum
cpp
100 200 200 200 100 0
忽略最后0作为我们考虑的最后一个索引仅用于清晰目的。
所以,这两个步骤都为我们找到差异数组:)
cpp
a[p]+=sum;
if((q+1)<=N) a[q+1]-=sum;
答案 3 :(得分:2)
蛮力方法要求我们迭代数组并将值添加到a到b范围内的每个元素。
我们可以执行此操作,而不是每次循环,我们在索引a处添加值,然后从索引b + 1中减去该值。最后,我们像从左到右添加一个前缀数组一样。
所以实际上发生的是我们添加的值从a反映到b。而且,由于我们从b + 1中减去了相同的值,因此在为b编制索引后就不会反映出来。
这个算法在O(n + m)中运行。
如果您对解释有任何疑问,可以观看此视频,该视频很好地解释了算法,并通过在少数测试案例上空运行算法来帮助更好地理解算法。短短7分钟的视频:)。
答案 4 :(得分:1)
以下代码在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;
}
答案 5 :(得分:1)
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 :)
答案 6 :(得分:0)
不是将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元素的起始索引类似于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在应用前缀sum之后添加到所有元素,直到末尾,但是我们不必在末尾添加k。因此,要取消此效果,我们还必须在b + 1索引之后添加-K,以便仅在[a,b]范围内才会有K个元素的添加效果。
A [b + 1] = A [b] -k //删除先前在bth索引之后添加的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 以获得详细说明。< / p>
答案 7 :(得分:0)
它使用数组差异的概念。将值添加到给定范围(i,j,k)是一个新概念。 i和j指定范围,k是要添加的值。 如果您检查链接,它将很有帮助。 https://www.geeksforgeeks.org/difference-array-range-update-query-o1