实现一个队列,其中push_rear(),pop_front()和get_min()都是恒定时间操作


76

我遇到了这个问题: 实现一个队列,其中push_rear(),pop_front()和get_min()都是恒定时间操作。

我最初想到的是,使用最小堆数据结构对get_min()具有O(1)复杂度。但是push_rear()和pop_front()将为O(log(n))。

有谁知道实现具有O(1)push(),pop()和min()的队列的最佳方法是什么?

我对此进行了搜索,并想指出这个Algorithm Geeks线程。但是似乎所有解决方案都没有一个遵循恒定时间规则,这三个方法分别是push(),pop()和min()。

感谢所有的建议。


您是否对所有这些都使用摊销O(1)时限还好,还是必须是最坏情况下的时限?
templatetypedef

我不确定,这是一个Google面试问题,我最初是在careercup.com/question?id=7263132上看到的...。感觉这个问题意味着最坏的时间范围。看来不可能吗?

@ bits-不,这肯定有可能,我现在正在解决它。:-)我正在考虑使用笛卡尔树来执行此操作-这使您可以进行O(1)摊销插入和O(1)查找,并且我几乎也可以使用O(1)摊销删除。但是,如果您正在寻找最坏的情况,我将改变我的方法。
templatetypedef

好,现在在下面查看Kdoto的答案;我现在可以确定,最坏的情况可能不会发生。因此,也许Google员工必须在寻找摊销O(1)。编辑:好的,作为Kdoto答案注释中的templatetypedef指针,证明不正确。注意。

不太确定,我的证明不正确。但是我不认为已经为所有操作找到了O(1),无论是否摊销。而且我怀疑这是不可能的。
Olhovsky 2011年

Answers:


99

您可以使用O(1)pop(),push()和get_min()来实现堆栈:只需将当前最小值与每个元素一起存储。因此,例如,堆栈[4,2,5,1](顶部为1)变为[(4,4), (2,2), (5,2), (1,1)]

然后,您可以使用两个堆栈来实现队列。推入一个堆栈,从另一个堆栈弹出;如果在弹出期间第二个堆栈为空,则将所有元素从第一个堆栈移至第二个。

例如,对于一个pop请求,将所有元素从第一个堆栈中移出[(4,4), (2,2), (5,2), (1,1)],第二个堆栈将为[(1,1), (5,1), (2,1), (4,1)]。现在从第二个堆栈返回top元素。

要找到队列的最小元素,请查看各个最小堆栈的最小两个元素,然后取这两个值中的最小值。(当然,这里有一些额外的逻辑,以防其中一个堆栈为空,但这并不是很难解决的问题)。

这将有O(1)get_min()push()摊销O(1) pop()


10
使用两个堆栈实现队列如何给您摊销O(1)pop?
templatetypedef

7
@template每个元素只能从一个堆栈移动到另一个堆栈一次。
adamax 2011年

6
如果将“当前最小值与元素一起存储”,并从队列中弹出最小值,那么您如何知道在O(1)时间内新的最小值?
Olhovsky 2011年

4
@adamax我听不懂第三部分。您的最低要求如何运作。如您所见,这里有太多评论。为什么不就您的算法步骤提供一个小例子。这将有助于您了解算法。
UmmaGumma 2011年

7
@adamax我终于明白了您的解决方案。您应该增加您的解释,即当我们将元素从第一个结构移动到第二个结构时,我们应该重新计算第二个元素的值。顺便说一句,正如我在答案中所示,可以在o(1)中进行所有这些操作,而不必在O(1)中进行摊销。:)
UmmaGumma 2011年

28

好的-我想我有一个答案,可以将所有这些操作都以O(1)摊销,这意味着任何一个操作最多可占用O(n),但是n个操作的任何序列每次操作都需要O(1)时间。

想法是将数据存储为笛卡尔树。这是一个遵循min-heap属性(每个节点不大于其子​​节点)的二叉树,并且以这样的方式进行排序:对节点的有序遍历会以与添加节点相同的顺序为您返回节点。例如,这是序列的笛卡尔树2 1 4 3 5

       1
     /   \
    2      3
          / \
         4   5

可以使用以下过程在O(1)摊销时间内将元素插入到笛卡尔树中。查看树的右脊(从根到最右边的叶子的路径,总是通过向右走来形成)。从最右边的节点开始,沿该路径向上扫描,直到找到第一个小于要插入的节点的节点。
更改该节点,使其右子节点成为此新节点,然后使该节点的前右子节点成为刚添加的节点的左子节点。例如,假设我们要在上面的树中插入2的另一个副本。我们沿着5和3穿过右脊,但由于1 <2而停在1之下。然后将树更改为如下所示:

       1
     /   \
    2      2
          /
         3
        / \
       4   5

请注意,有序遍历给出2 1 4 3 5 2,这是我们添加值的顺序。

因为我们可以创建等于树右脊中节点数的潜在函数,所以它以摊销O(1)运行。插入节点所需的实时时间为1加上我们考虑的主干中的节点数(称为k)。一旦找到插入节点的位置,由于我们访问的k个节点中的每一个不再位于正确的脊线上,并且新的节点就在其位置,因此脊的大小将缩小k-1。对于已摊销的O(1)插入项,得出的摊销成本为1 + k +(1-k)= 2 = O(1)。另一种思考方式是,将节点移出正确的书脊后,它就不再是正确的书脊的一部分,因此我们不再需要再次将其移开。由于n个节点中的每个节点最多只能移动一次,因此这意味着n次插入最多可以进行n次移动,

要执行出队步骤,我们只需从笛卡尔树中删除最左边的节点即可。如果此节点是叶子,则操作完成。否则,该节点只能有一个孩子(正确的孩子),因此我们将节点替换为其正确的孩子。假设我们跟踪最左边的节点在哪里,那么此步骤将花费O(1)时间。但是,在删除最左边的节点并将其替换为右边的子节点之后,我们可能不知道新的最左边的节点在哪里。为了解决这个问题,我们只需从刚移到最左侧孩子的新节点处开始,沿着树的左脊柱走即可。我声称它仍在O(1)摊销时间内运行。为了看到这一点,我声称在这些遍历中的任何一次遍历中,最多只能访问一个节点以找到最左边的节点。要看到这一点,请注意,以这种方式访问​​节点后,我们可能需要再次查看的唯一方法是将其从最左侧节点的子级移动到最左侧节点。但是访问的所有节点都是最左边节点的父节点,因此不会发生这种情况。因此,在此过程中,每个节点最多访问一次,并且弹出操作以O(1)运行。

我们可以在O(1)中执行find-min,因为笛卡尔树使我们可以免费访问树的最小元素。它是树的根。

最后,要查看节点以插入时的顺序返回,请注意,笛卡尔树始终存储其元素,以便按顺序遍历可以按顺序访问它们。由于我们总是在每个步骤中删除最左边的节点,而这是有序遍历的第一个元素,因此我们总是按插入顺序将节点取回。

简而言之,我们得到O(1)摊销的推和弹出,以及O(1)最坏情况的find-min。

如果我能提出最坏的O(1)实现,我一定会发布。这是一个很大的问题。感谢您发布!


我仍在考虑您的pop是否真的摊销了O(1)。确认后,我一定会赞成这个答案。如果其他人也帮助验证了此答案,那将是很好的。
Olhovsky 2011年

@ Kdoto-想一想,如果要获得O(1)摊销出队,则需要每个节点都存储一个父指针,因为这样一来,当您删除叶子时,可以将指针更新为指针中最左边的节点。最坏情况下的树O(1)。
templatetypedef

23

好的,这是一种解决方案。

首先,我们需要一些在0(1)中提供push_back(),push_front(),pop_back()和pop_front()的东西。使用数组和2个迭代器很容易实现。第一个迭代器指向前面,第二个指向后面。我们称这种东西为双端队列。

这是伪代码:

class MyQueue//Our data structure
{
    deque D;//We need 2 deque objects
    deque Min;

    push(element)//pushing element to MyQueue
    {
        D.push_back(element);
        while(Min.is_not_empty() and Min.back()>element)
             Min.pop_back();
        Min.push_back(element);
    }
    pop()//poping MyQueue
    {
         if(Min.front()==D.front() )
            Min.pop_front();
         D.pop_front();
    }

    min()
    {
         return Min.front();
    }
}

说明:

示例让我们将数字[12,5,10,7,11,19]推送到我们的MyQueue

1)推12

D [12]
Min[12]

2)推5

D[12,5]
Min[5] //5>12 so 12 removed

3)推10

D[12,5,10]
Min[5,10]

4)推7

D[12,5,10,7]
Min[5,7]

6)推11

D[12,5,10,7,11]
Min[5,7,11]

7)推19

D[12,5,10,7,11,19]
Min[5,7,11,19]

现在我们叫pop_front()

我们有

 D[5,10,7,11,19]
 Min[5,7,11,19]

最小为5

让我们再次调用pop_front()

说明:pop_front将从D删除5,但也会弹出Min的front元素,因为它等于D的front元素(5)。

 D[10,7,11,19]
 Min[7,11,19]

最少是7。


看来,如果你把2,3,1个然后get_min回报2,而不是1
adamax

@adamax糟糕:)。你懂我 我更正了push()。现在它可以正常工作,但不能在0(1)中工作。它正在像您一样在O(1)中摊销:)。
UmmaGumma 2011年

2
@UmmaGumma,干得好!尽管进行了较小的校正,但当从堆栈中弹出12时其5 <12。
搜寻者2014年

3

使用一个双端队列(A)存储元素,使用另一个双端队列(B)存储最小值。

将x排入队列后,将其push_back到A,并保持pop_backing B直到B的背面小于x,然后将push_back x到B。

将A出队时,将pop_front A作为返回值,如果它等于B的前部,则也将pop_front B用作返回值。

当获得最小值A时,将B的前部用作返回值。

dequeue和getmin显然是O(1)。对于入队操作,请考虑n个元素的push_back。因为每个元素将留在B中或从B中弹出一次,所以存在n个push_back到A,n个push_back到B以及最多n个pop_back的B。总共有O(3n)个操作,因此摊销成本为O (1)也适合入队。

最后,该算法起作用的原因是,当您将x排队到A时,如果B中的元素大于x,则它们将永远不再是最小值,因为x在队列A中的停留时间比在B中的任何元素(队列是FIFO)。因此,在将x推入B之前,我们需要从B中弹出大于x的元素(从背面)。

from collections import deque


class MinQueue(deque):
    def __init__(self):
        deque.__init__(self)
        self.minq = deque()

    def push_rear(self, x):
        self.append(x)
        while len(self.minq) > 0 and self.minq[-1] > x:
            self.minq.pop()
        self.minq.append(x)

    def pop_front(self):
        x = self.popleft()
        if self.minq[0] == x:
            self.minq.popleft()
        return(x)

    def get_min(self):
        return(self.minq[0])

1

如果您不介意存储一些额外的数据,则存储最小值应该是微不足道的。如果新元素或删除的元素为最小值,则push和pop可以更新该值,而返回最小值与获取变量的值一样简单。

假设get_min()不会更改数据;如果您希望使用pop_min()之类的东西(即删除最小元素),则可以简单地存储指向实际元素和该元素之前的元素的指针(如果有的话),并使用push_rear()和pop_front()进行相应的更新也一样

评论后编辑:

显然,在这些操作的最小变化的情况下,这会导致O(n)推送和弹出,因此不能严格满足要求。


1
因为您必须扫描所有元素以找到新的最小值,所以这不会给您O(n)弹出消息吗?
templatetypedef

2
我认为get_min()实际上不会弹出数据。但是pop_front()会弹出数据。可以说前端节点也是min节点,因此它弹出了。现在我们如何保持min属性不变?

啊,不错的电话...尽管您说得对,@ bits,但在您推送新的最小值或弹出当前最小值的情况下只有O(n)。如果必须是最坏的情况O(1),我不知道这是可能的,但是我很乐意看到其他情况。
安迪·米库拉


1

您实际上可以使用LinkedList维护队列。

LinkedList中的每个元素都是Type

class LinkedListElement
{
   LinkedListElement next;
   int currentMin;
}

您可以有两个指针,一个指向起点,另一个指向终点。

如果将元素添加到队列的开头。检查开始指针和要插入的节点。如果要插入的currentmin的节点小于开始的currentmin,则要插入的电流min的节点为最小值。否则用启动currentmin更新currentmin。

重复同样的步骤。


0
#include <iostream>
#include <queue>
#include <deque>
using namespace std;

queue<int> main_queue;
deque<int> min_queue;

void clearQueue(deque<int> &q)
{
  while(q.empty() == false) q.pop_front();
}

void PushRear(int elem)
{
  main_queue.push(elem);

  if(min_queue.empty() == false && elem < min_queue.front())
  {
      clearQueue(min_queue);
  }

  while(min_queue.empty() == false && elem < min_queue.back())
  {
      min_queue.pop_back();
  }

  min_queue.push_back(elem);
}

void PopFront() 
{
  int elem = main_queue.front();
  main_queue.pop();

  if (elem == min_queue.front())
  {
       min_queue.pop_front();
  }
}

int GetMin() 
{ 
  return min_queue.front(); 
}

int main()
{
  PushRear(1);
  PushRear(-1);
  PushRear(2);

  cout<<GetMin()<<endl;
  PopFront();
  PopFront();
  cout<<GetMin()<<endl;

  return 0;
}

如果没有附带清楚说明代码为什么正确的解释,则发布代码是不好的。
理查德

该代码很容易解释。如果您想要解释,可以请问而不是投反对票。
TheMan 2014年

我最喜欢StackOverflow的一项特质是这里回答的高质量。当我访问其他网站时,似乎每个发布者都像您一样抛出大量“自我解释性代码”。不可避免地,其中一些是错误的,每个人都需要时间来理解和验证。好的答案会带您完成验证过程,并抢先回答您可能遇到的问题。很难记得回来再检查这些事情,因此我更喜欢投反对票,然后中和或投票。
理查德

AFAICT,这与早在一个多月前由江来描述的作为源代码给出的算法相同。
j_random_hacker

0

该解决方案包含2个队列:
1. main_q-存储输入数字。
2. min_q-按照我们将要描述的某些规则存储最小值(出现在函数MainQ.enqueue(x),MainQ.dequeue(),MainQ.get_min()中)。

这是Python中的代码。使用列表实现队列。
主要思想在于MainQ.enqueue(x),MainQ.dequeue(),MainQ.get_min()函数。
一个关键的假设是清空队列需要o(0)。
最后提供了一个测试。

import numbers

class EmptyQueueException(Exception):
    pass

class BaseQ():
    def __init__(self):
        self.l = list()

    def enqueue(self, x):
        assert isinstance(x, numbers.Number)
        self.l.append(x)

    def dequeue(self):
        return self.l.pop(0)

    def peek_first(self):
        return self.l[0]

    def peek_last(self):
        return self.l[len(self.l)-1]

    def empty(self):
        return self.l==None or len(self.l)==0

    def clear(self):
        self.l=[]

class MainQ(BaseQ):
    def __init__(self, min_q):
        super().__init__()
        self.min_q = min_q

    def enqueue(self, x):
        super().enqueue(x)
        if self.min_q.empty():
            self.min_q.enqueue(x)
        elif x > self.min_q.peek_last():
            self.min_q.enqueue(x)
        else: # x <= self.min_q.peek_last():
            self.min_q.clear()
            self.min_q.enqueue(x)

    def dequeue(self):
        if self.empty():
            raise EmptyQueueException("Queue is empty")
        x = super().dequeue()
        if x == self.min_q.peek_first():
            self.min_q.dequeue()
        return x

    def get_min(self):
        if self.empty():
            raise EmptyQueueException("Queue is empty, NO minimum")
        return self.min_q.peek_first()

INPUT_NUMS = (("+", 5), ("+", 10), ("+", 3), ("+", 6), ("+", 1), ("+", 2), ("+", 4), ("+", -4), ("+", 100), ("+", -40),
              ("-",None), ("-",None), ("-",None), ("+",-400), ("+",90), ("-",None),
              ("-",None), ("-",None), ("-",None), ("-",None), ("-",None), ("-",None), ("-",None), ("-",None))

if __name__ == '__main__':
    min_q = BaseQ()
    main_q = MainQ(min_q)

    try:
        for operator, i in INPUT_NUMS:
            if operator=="+":
                main_q.enqueue(i)
                print("Added {} ; Min is: {}".format(i,main_q.get_min()))
                print("main_q = {}".format(main_q.l))
                print("min_q = {}".format(main_q.min_q.l))
                print("==========")
            else:
                x = main_q.dequeue()
                print("Removed {} ; Min is: {}".format(x,main_q.get_min()))
                print("main_q = {}".format(main_q.l))
                print("min_q = {}".format(main_q.min_q.l))
                print("==========")
    except Exception as e:
        print("exception: {}".format(e))

以上测试的输出为:

"C:\Program Files\Python35\python.exe" C:/dev/python/py3_pocs/proj1/priority_queue.py
Added 5 ; Min is: 5
main_q = [5]
min_q = [5]
==========
Added 10 ; Min is: 5
main_q = [5, 10]
min_q = [5, 10]
==========
Added 3 ; Min is: 3
main_q = [5, 10, 3]
min_q = [3]
==========
Added 6 ; Min is: 3
main_q = [5, 10, 3, 6]
min_q = [3, 6]
==========
Added 1 ; Min is: 1
main_q = [5, 10, 3, 6, 1]
min_q = [1]
==========
Added 2 ; Min is: 1
main_q = [5, 10, 3, 6, 1, 2]
min_q = [1, 2]
==========
Added 4 ; Min is: 1
main_q = [5, 10, 3, 6, 1, 2, 4]
min_q = [1, 2, 4]
==========
Added -4 ; Min is: -4
main_q = [5, 10, 3, 6, 1, 2, 4, -4]
min_q = [-4]
==========
Added 100 ; Min is: -4
main_q = [5, 10, 3, 6, 1, 2, 4, -4, 100]
min_q = [-4, 100]
==========
Added -40 ; Min is: -40
main_q = [5, 10, 3, 6, 1, 2, 4, -4, 100, -40]
min_q = [-40]
==========
Removed 5 ; Min is: -40
main_q = [10, 3, 6, 1, 2, 4, -4, 100, -40]
min_q = [-40]
==========
Removed 10 ; Min is: -40
main_q = [3, 6, 1, 2, 4, -4, 100, -40]
min_q = [-40]
==========
Removed 3 ; Min is: -40
main_q = [6, 1, 2, 4, -4, 100, -40]
min_q = [-40]
==========
Added -400 ; Min is: -400
main_q = [6, 1, 2, 4, -4, 100, -40, -400]
min_q = [-400]
==========
Added 90 ; Min is: -400
main_q = [6, 1, 2, 4, -4, 100, -40, -400, 90]
min_q = [-400, 90]
==========
Removed 6 ; Min is: -400
main_q = [1, 2, 4, -4, 100, -40, -400, 90]
min_q = [-400, 90]
==========
Removed 1 ; Min is: -400
main_q = [2, 4, -4, 100, -40, -400, 90]
min_q = [-400, 90]
==========
Removed 2 ; Min is: -400
main_q = [4, -4, 100, -40, -400, 90]
min_q = [-400, 90]
==========
Removed 4 ; Min is: -400
main_q = [-4, 100, -40, -400, 90]
min_q = [-400, 90]
==========
Removed -4 ; Min is: -400
main_q = [100, -40, -400, 90]
min_q = [-400, 90]
==========
Removed 100 ; Min is: -400
main_q = [-40, -400, 90]
min_q = [-400, 90]
==========
Removed -40 ; Min is: -400
main_q = [-400, 90]
min_q = [-400, 90]
==========
Removed -400 ; Min is: 90
main_q = [90]
min_q = [90]
==========
exception: Queue is empty, NO minimum

Process finished with exit code 0

0

Java实现

import java.io.*;
import java.util.*;

public class queueMin {
    static class stack {

        private Node<Integer> head;

        public void push(int data) {
            Node<Integer> newNode = new Node<Integer>(data);
            if(null == head) {
                head = newNode;
            } else {
                Node<Integer> prev = head;
                head = newNode;
                head.setNext(prev);
            }
        }

        public int pop() {
            int data = -1;
            if(null == head){
                System.out.println("Error Nothing to pop");
            } else {
                data = head.getData();
                head = head.getNext();
            }

            return data;
        }

        public int peek(){
            if(null == head){
                System.out.println("Error Nothing to pop");
                return -1;
            } else {
                return head.getData();
            }
        }

        public boolean isEmpty(){
            return null == head;
        }
    }

    static class stackMin extends stack {
        private stack s2;

        public stackMin(){
            s2 = new stack();
        }

        public void push(int data){
            if(data <= getMin()){
                s2.push(data);
            }

            super.push(data);
        }

        public int pop(){
            int value = super.pop();
            if(value == getMin()) {
                s2.pop();
            }
            return value;
        }

        public int getMin(){
            if(s2.isEmpty()) {
                return Integer.MAX_VALUE;
            }
            return s2.peek();
        }
    }

     static class Queue {

        private stackMin s1, s2;

        public Queue(){
            s1 = new stackMin();
            s2 = new stackMin();
        }

        public  void enQueue(int data) {
            s1.push(data);
        }

        public  int deQueue() {
            if(s2.isEmpty()) {
                while(!s1.isEmpty()) {
                    s2.push(s1.pop());
                }
            }

            return s2.pop();
        }

        public int getMin(){
            return Math.min(s1.isEmpty() ? Integer.MAX_VALUE : s1.getMin(), s2.isEmpty() ? Integer.MAX_VALUE : s2.getMin());
        }

    }



   static class Node<T> {
        private T data;
        private T min;
        private Node<T> next;

        public Node(T data){
            this.data = data;
            this.next = null;
        }


        public void setNext(Node<T> next){
            this.next = next;
        }

        public T getData(){
            return this.data;
        }

        public Node<T> getNext(){
            return this.next;
        }

        public void setMin(T min){
            this.min = min;
        }

        public T getMin(){
            return this.min;
        }
    }

    public static void main(String args[]){
       try {
           FastScanner in = newInput();
           PrintWriter out = newOutput();
          // System.out.println(out);
           Queue q = new Queue();
           int t = in.nextInt();
           while(t-- > 0) {
               String[] inp = in.nextLine().split(" ");
               switch (inp[0]) {
                   case "+":
                       q.enQueue(Integer.parseInt(inp[1]));
                       break;
                   case "-":
                       q.deQueue();
                       break;
                   case "?":
                       out.println(q.getMin());
                   default:
                       break;
               }
           }
           out.flush();
           out.close();

       } catch(IOException e){
          e.printStackTrace();
       }
    }

    static class FastScanner {
        static BufferedReader br;
        static StringTokenizer st;

        FastScanner(File f) {
            try {
                br = new BufferedReader(new FileReader(f));
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
        }
        public FastScanner(InputStream f) {
            br = new BufferedReader(new InputStreamReader(f));
        }
        String next() {
            while (st == null || !st.hasMoreTokens()) {
                try {
                    st = new StringTokenizer(br.readLine());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return st.nextToken();
        }

        String nextLine(){
            String str = "";
            try {
                str = br.readLine();
            } catch (IOException e) {
                e.printStackTrace();
            }
            return str;
        }

        int nextInt() {
            return Integer.parseInt(next());
        }
        long nextLong() {
            return Long.parseLong(next());
        }
        double nextDoulbe() {
            return Double.parseDouble(next());
        }
    }

    static FastScanner newInput() throws IOException {
        if (System.getProperty("JUDGE") != null) {
            return new FastScanner(new File("input.txt"));
        } else {
            return new FastScanner(System.in);
        }
    }
    static PrintWriter newOutput() throws IOException {
        if (System.getProperty("JUDGE") != null) {
            return new PrintWriter("output.txt");
        } else {
            return new PrintWriter(System.out);
        }
    }
}

0

JavaScript实现

(请给adamax的解决方案以创意;我为该实现提供了一个宽松的基础。跳到底部以查看完整注释的代码或通读下面的常规步骤。请注意,这是在O(1)恒定时间内找到最大值,而不是该最小值--easy改变了):

一般的想法是在构造时创建两个堆栈MaxQueue(我使用链接列表作为基础Stack数据结构-不包括在代码中;但是Stack只要使用O(1)插入/删除实现,任何操作都可以)。我们主要pop从(dqStack)到之一,而我们主要push到(eqStack)。


插入:O(1)最坏的情况

对于 enqueue,如果theMaxQueue为空,我们将把push值todqStack元组中的当前最大值(相同的值,因为它是中的唯一值MaxQueue)一起使用;例如:

const m = new MaxQueue();

m.enqueue(6);

/*
the dqStack now looks like:
[6, 6] - [value, max]
*/

如果的MaxQueue值不为空,push则将设为eqStack

m.enqueue(7);
m.enqueue(8);

/*
dqStack:         eqStack: 8
         [6, 6]           7 - just the value
*/

然后,更新元组中的最大值。

/*
dqStack:         eqStack: 8
         [6, 8]           7
*/


删除:O(1)摊销

对于dequeue我们popdqStack从元组返回值。

m.dequeue();
> 6

// equivalent to:
/*
const tuple = m.dqStack.pop() // [6, 8]
tuple[0];
> 6
*/

那如果 dqStack是空的,将在所有的值eqStackdqStack,例如:

// if we build a MaxQueue
const maxQ = new MaxQueue(3, 5, 2, 4, 1);

/*
the stacks will look like:

dqStack:         eqStack: 1
                          4
                          2
         [3, 5]           5
*/

随着每个值的移动,我们将检查它是否大于最大值 到目前为止,并将其存储在每个元组中:

maxQ.dequeue(); // pops from dqStack (now empty), so move all from eqStack to dqStack
> 3

// as dequeue moves one value over, it checks if it's greater than the ***previous max*** and stores the max at tuple[1], i.e., [data, max]:
/*
dqStack: [5, 5] => 5 > 4 - update                          eqStack:
         [2, 4] => 2 < 4 - no update                         
         [4, 4] => 4 > 1 - update                            
         [1, 1] => 1st value moved over so max is itself            empty
*/

因为每个值都移到 dqStack 最多一次,所以可以说dequeue具有O(1)摊销时间复杂度。


找出最大值:O(1)最坏的情况

然后,在任何时间点,我们都可以调用getMax以O(1)恒定时间检索当前最大值。只要的MaxQueue值不为空,就很容易将最大值从中的下一个元组中拉出dqStack

maxQ.getMax();
> 5

// equivalent to calling peek on the dqStack and pulling out the maximum value:
/*
const peekedTuple = maxQ.dqStack.peek(); // [5, 5]
peekedTuple[1];
> 5
*/


class MaxQueue {
  constructor(...data) {
    // create a dequeue Stack from which we'll pop
    this.dqStack = new Stack();
    // create an enqueue Stack to which we'll push
    this.eqStack = new Stack();
    // if enqueueing data at construction, iterate through data and enqueue each
    if (data.length) for (const datum of data) this.enqueue(datum);
  }
  enqueue(data) { // O(1) constant insertion time
    // if the MaxQueue is empty,
    if (!this.peek()) {
      // push data to the dequeue Stack and indicate it's the max;
      this.dqStack.push([data, data]); // e.g., enqueue(8) ==> [data: 8, max: 8]
    } else {
      // otherwise, the MaxQueue is not empty; push data to enqueue Stack
      this.eqStack.push(data);
      // save a reference to the tuple that's next in line to be dequeued
      const next = this.dqStack.peek();
      // if the enqueueing data is > the max in that tuple, update it
      if (data > next[1]) next[1] = data;
    }
  }
  moveAllFromEqToDq() { // O(1) amortized as each value will move at most once
    // start max at -Infinity for comparison with the first value
    let max = -Infinity;
    // until enqueue Stack is empty,
    while (this.eqStack.peek()) {
      // pop from enqueue Stack and save its data
      const data = this.eqStack.pop();
      // if data is > max, set max to data
      if (data > max) max = data;
      // push to dequeue Stack and indicate the current max; e.g., [data: 7: max: 8]
      this.dqStack.push([data, max]);
    }
  }
  dequeue() { // O(1) amortized deletion due to calling moveAllFromEqToDq from time-to-time
    // if the MaxQueue is empty, return undefined
    if (!this.peek()) return;
    // pop from the dequeue Stack and save it's data
    const [data] = this.dqStack.pop();
    // if there's no data left in dequeue Stack, move all data from enqueue Stack
    if (!this.dqStack.peek()) this.moveAllFromEqToDq();
    // return the data
    return data;
  }
  peek() { // O(1) constant peek time
    // if the MaxQueue is empty, return undefined
    if (!this.dqStack.peek()) return;
    // peek at dequeue Stack and return its data
    return this.dqStack.peek()[0];
  }
  getMax() { // O(1) constant time to find maximum value
    // if the MaxQueue is empty, return undefined
    if (!this.peek()) return;
    // peek at dequeue Stack and return the current max
    return this.dqStack.peek()[1];
  }
}


0

我们知道推入和弹出是恒定时间操作[准确地说是O(1)]。

但是,当我们想到get_min()[即在队列中找到当前的最小数目]时,通常想到的第一件事是每次请求最小元素时都搜索整个队列。但这永远不会给出恒定的时间运行,这是问题的主要目的。

通常在面试中经常问到这个问题,所以您必须知道窍门

为此,我们必须再使用两个队列来跟踪最小元素,并且必须继续对这两个队列进行修改,因为我们在队列上执行推和弹出操作,以便在O(1)时间内获得最小元素。

这是基于上述方法的自描述伪代码。

    Queue q, minq1, minq2;
    isMinq1Current=true;   
    void push(int a)
    {
      q.push(a);
      if(isMinq1Current)
      {
        if(minq1.empty) minq1.push(a);
        else
        {
          while(!minq1.empty && minq1.top < =a) minq2.push(minq1.pop());
          minq2.push(a);
          while(!minq1.empty) minq1.pop();
          isMinq1Current=false;
        }
      }
      else
      {
        //mirror if(isMinq1Current) branch. 
      }
    }
     
    int pop()
    { 
      int a = q.pop();
      if(isMinq1Current)
      {
        if(a==minq1.top) minq1.pop();
      }
      else
      {
        //mirror if(isMinq1Current) branch.    
      }
    return a;
    }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.