问题描述
这个问题是我很久以前问过的another one的后续内容:
我们得到了一个整数数组和另一个数字k,我们需要找到总和等于k的连续子数组的总数。例如,对于输入
[1,1,1]
和k=2
,预期输出为2
。
PS:BTW如果所有值都不为负,则有更好的算法。它不需要额外的内存。
虽然我当时对此并没有太多考虑,但我对此感到很好奇。恕我直言,我们将需要额外的内存。如果所有输入值均为非负数,我们的运行(前缀)总和将继续增加,因此,可以肯定的是,我们不需要unordered_map
来存储特定总和的频率。但是,我们仍然需要额外的内存(也许是unordered_set
)来存储我们沿途运行的(前缀)和。这显然与@talex所说的相矛盾。
有人可以确认我们是否绝对需要额外的内存,还是可以避免?
谢谢!
解决方法
让我们从一个稍微简单的问题开始:所有值均为正(无零)。在这种情况下,子数组可以重叠,但不能包含彼此。
即:arr = 2 1 5 1 1 5 1 2
,总和= 8
2 1 5 1 1 5 1 2
|---|
|-----|
|-----|
|---|
但是这种情况永远不会发生:
* * * * * * *
|-------|
|---|
考虑到这一点,有一种算法不需要额外的空间(嗯。O(1)
空间)并且具有O(n)
的时间复杂度。想法应具有指示当前序列和当前序列之和的左右索引。
- 如果总和为
k
,则增加计数器,将left
和right
前进 - 如果总和小于
k
,则提前right
- 其他推进
left
现在,如果零为零,则间隔可以包含另一个,但前提是零位于间隔的边缘。
要适应非负数:
执行上述操作,除了:
- 前进
left
时跳过零 - 如果总和为
k
:- 计算
right
右边连续的零,假设zeroes_right_count
- 计数
left
左侧的连续零。可以说zeroes_left_count
- 而不是像以前那样增加计数,而是将计数器增加:
(zeroes_left_count + 1) * (zeroes_right_count + 1)
- 计算
示例:
... 7 0 0 5 1 2 0 0 0 9 ...
^ ^
left right
在这里,我们在左边有2个零,在右边有3个零。这使得(2 + 1) * (3 + 1) = 12
序列的总和为8
:
5 1 2
5 1 2 0
5 1 2 0 0
5 1 2 0 0 0
0 5 1 2
0 5 1 2 0
0 5 1 2 0 0
0 5 1 2 0 0 0
0 0 5 1 2
0 0 5 1 2 0
0 0 5 1 2 0 0
0 0 5 1 2 0 0 0
,
我认为该算法可以使用O(1)
空间来工作。
我们维护两个指针,分别指向当前子序列的开始和结束以及当前子序列的总和。最初,两个指针都指向array[0]
,并且总和显然设置为array[0]
。
使结束指针前进(从而将子序列向右扩展),并将总和增加它所指向的值,直到总和超过k
。然后前进开始指针(因此从左侧缩小子序列),并减小总和,直到该总和低于k
。继续执行此操作,直到结束指针到达数组的末尾。跟踪总和正好为k
的次数。