Блог пользователя k1r1t0

Автор k1r1t0, история, 14 месяцев назад, По-английски

Here I will describe a way to maintain deque that will allow us to find the smallest element in deque at any time. Maybe this technique is well-known, but I couldn’t find any info about it, so decided to post it there.

Minimum Stack and Minimum Queue

First, we want to maintain stack with the ability to find its smallest element whenever we want. Let's store minimums on the corresponding prefix of stack along with elements. This way we can easily push/pop elements and find minimum in $$$O(1)$$$.

Minimum Queue can be maintained using two Minimum Stacks: one stack will store some elements from beginning, and the other from the end. We will push elements to the second stack and pop them from the first. If the first stack becomes empty, then we move all elements from the second stack into the first. It will work in $$$O(N)$$$ amortized.

Minimum Deque

Actually, we can maintain Minimum Deque the same way we maintain Minimum Queue, but it will be too slow. The problem is the number of moved elements: in the worst case we will move all elements every operation by removing first and last element alternately. So instead of moving all elements let's move only a half of them at a time. Turns out it works in $$$O(N)$$$!

But how to prove it? Let $$$k_i$$$ be the number of elements deque contained right before $$$i_{th}$$$ operation, the algorithm above obviously works in $$$O(N + \sum k_i)$$$. Also $$$k_i = \frac{k_{i-1}}{2} + q_i$$$, where $$$q_i$$$ is the number of elements added between operations $$$i-1$$$ and $$$i$$$. We can see that $$$q_i$$$ contributes $$$q_i$$$ to $$$k_i$$$, $$$\frac{q_i}{2}$$$ to $$$k_{i+1}$$$, $$$\frac{q_i}{4}$$$ to $$$k_{i+2}$$$ and so on. It follows that $$$q_i$$$ contributes $$$O(q_i)$$$ to $$$O(\sum k_i)$$$. $$$\sum q_i$$$ is obviously $$$\leq N$$$, so $$$O(\sum k_i) = O(N)$$$. That's why the algorithm above is $$$O(N)$$$ amortized.

Implementation
  • Проголосовать: нравится
  • +210
  • Проголосовать: не нравится

»
14 месяцев назад, # |
  Проголосовать: нравится -38 Проголосовать: не нравится

iirc this is well known in Japan and it is called SWAG (Sliding Window AGgregation). it's a pretty cool name I think

»
14 месяцев назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится

Aha, it can solve problems with the two pointers trick. Because the two pointers trick needs to remove $$$value(left)$$$, and so it can help to remove while maintaining $$$\min{value}$$$. I once saw it in a contest's tutorial (not on CodeForces), but I didn't think about it carefully, though.

»
14 месяцев назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится

Is it different from priority_queue ?

  • »
    »
    14 месяцев назад, # ^ |
      Проголосовать: нравится +8 Проголосовать: не нравится

    Did you read article?

  • »
    »
    14 месяцев назад, # ^ |
      Проголосовать: нравится +8 Проголосовать: не нравится

    Yes, but unlike the priority queue, when you delete an element in a minimum deque, you can delete the first element or the last one, when in the priority queue you can only delete the smallest one, in the priority queue you lose that order of the elements.

»
14 месяцев назад, # |
  Проголосовать: нравится +62 Проголосовать: не нравится

this is well known in Croatia and it is called Monotnic Deque

  • »
    »
    14 месяцев назад, # ^ |
      Проголосовать: нравится -10 Проголосовать: не нравится

    I think monotonic queue is a different thing? Doesn't monotonic queue throw out unnecessary elements? I think it does if I am not mistaken

    • »
      »
      »
      14 месяцев назад, # ^ |
        Проголосовать: нравится +17 Проголосовать: не нравится

      And where did I mention monotonic queue :P

      • »
        »
        »
        »
        14 месяцев назад, # ^ |
          Проголосовать: нравится +4 Проголосовать: не нравится

        Oh yes I guess I am mistaken :facepalm: I think I was just thinking about another technique with a similar name

        Anyways, it should be well known in various countries with various names.

»
14 месяцев назад, # |
  Проголосовать: нравится +9 Проголосовать: не нравится

This is well known in Romania. I don't think it has a specific name.

»
14 месяцев назад, # |
Rev. 2   Проголосовать: нравится +27 Проголосовать: не нравится

A seemingly easier argument: Consider $$$\operatorname{abs}((\mathrm{the\ size\ of\ the\ first\ queue}) - (\mathrm{the\ size\ of\ second\ queue}))$$$ as the potential function. push/pop front/back increases the potential by at most 1, and the rebalance zeroes the potential function.

»
14 месяцев назад, # |
  Проголосовать: нравится +30 Проголосовать: не нравится

One problem that require this ds RMI 2020 — Peru. The naive $$$O(nlogn)$$$ TLEs (time limit is $$$200ms$$$).

»
14 месяцев назад, # |
  Проголосовать: нравится +8 Проголосовать: не нравится

I think the best name I have heard for this technique came from rainboy who called it "stabbing points."

»
2 месяца назад, # |
Rev. 3   Проголосовать: нравится +30 Проголосовать: не нравится

I was curious about how the constant factors look like, this is what I got. Cost is the average of add and remove operation. I only count the monoid operations:

  • Monotone Stack: 0.5/op
  • Monotone Queue: 1/op
  • Monotone Deque (here): 1.5/op

Proof of last one: add/remove add 1 to potential (using the potential by ppavic), adding to the stack costs 1 by itself. 1 potential is 1 op when rebalancing.

The point is constant factor isn’t so bad so it could be used with less discretion.

Update: I realised the above analysis doesn't take account for the fact that you cannot perfectly rebalance if size is odd. Upto 1 op per removal is necessary depending on implementation.

See 2026F - Bermart Ice Cream for a problem that this trick could solve.