【问题标题】:Can we really avoid extra space when all the values are non-negative?当所有值都是非负数时,我们真的可以避免额外的空间吗?
【发布时间】:2020-08-30 00:50:27
【问题描述】:

这个问题是我很久以前问过的another one的后续问题:

给定一个整数数组和另一个数字 k,我们需要找到总和等于 k ​​的连续子数组的总数。例如,对于输入:[1,1,1]k=2,预期输出为 2

accepted answer@talex 中说:

PS:顺便说一句,如果所有值都是非负数,那么会有更好的算法。它不需要额外的内存。

虽然当时我并没有想太多,但我现在很好奇。恕我直言,我们需要额外的内存。如果所有输入值都是非负数,我们的运行(前缀)总和将继续增加,因此,当然,我们不需要unordered_map 来存储特定总和的频率。但是,我们仍然需要额外的内存(可能是unordered_set)来存储我们一路上得到的运行(前缀)总和。这显然与@talex 所说的相矛盾。

有人可以确认我们是否绝对确实需要额外的内存或者是否可以避免?

谢谢!

【问题讨论】:

  • 我记得以前见过这个问题,所以这一定是重复的。有人能找到吗?

标签: c++ algorithm memory space-complexity


【解决方案1】:

让我们从一个稍微简单的问题开始:所有值都是正数(没有零)。在这种情况下,子数组可以重叠,但不能相互包含。

即:arr = 2 1 5 1 1 5 1 2, Sum = 8

2 1 5 1 1 5 1 2
|---|
  |-----|
      |-----|
          |---|

但这种情况永远不会发生:

* * * * * * *
  |-------|
    |---|

考虑到这一点,算法不需要额外的空间(嗯..O(1) 空间)并且具有O(n) 时间复杂度。想法是有左右索引指示当前序列和当前序列的总和。

  • 如果总和为k,则增加计数器,推进leftright
  • 如果总和小于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

【讨论】:

    【解决方案2】:

    我认为这个算法可以工作,使用O(1) 空间。

    我们维护两个指向当前子序列的开始和结束的指针,以及当前子序列的和。最初,两个指针都指向array[0],而总和显然设置为array[0]

    推进结束指针(从而将子序列向右扩展),并将总和增加它指向的值,直到总和超过k。然后推进起始指针(从而从左边缩小子序列),并减少总和,直到总和低于k。继续这样做,直到结束指针到达数组的末尾。跟踪总和恰好是 k 的次数。

    【讨论】:

    • 由于这些值只是非负数,而不是严格正数,因此您需要考虑零。
    • @EricPostpischil 我们将此作为练习留给读者。调整方法来处理这个问题相当简单。
    猜你喜欢
    • 2021-11-17
    • 1970-01-01
    • 2021-05-13
    • 2020-06-19
    • 1970-01-01
    • 1970-01-01
    • 2016-07-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多