【问题标题】:Implement a queue in which push_rear(), pop_front() and get_min() are all constant time operations实现一个队列,其中 push_rear()、pop_front() 和 get_min() 都是常数时间操作
【发布时间】:2018-01-22 18:23:43
【问题描述】:

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

我最初考虑使用最小堆数据结构,它对 get_min() 具有 O(1) 的复杂性。但是 push_rear() 和 pop_front() 会是 O(log(n))。

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

我在谷歌上搜索过这个问题,并想指出这个Algorithm Geeks thread。但似乎没有一个解决方案对所有 3 种方法都遵循恒定时间规则:push()、pop() 和 min()。

感谢所有建议。

【问题讨论】:

  • 您是否同意所有这些的摊销 O(1) 时间界限,或者这些必须是最坏情况的时间界限?
  • 我不确定,它是一个谷歌面试问题,我最初在careercup.com/question?id=7263132 看到它。感觉这个问题意味着最坏的时间范围。这似乎是不可能的吗?
  • @bits- 不,这绝对是可能的,我现在正在努力。 :-) 我正在考虑使用笛卡尔树来做到这一点——这给了你 O(1) 分期插入和 O(1) 查找,而且我几乎得到了 O(1) 分期删除工作。但是,如果您正在寻找最坏情况的界限,我会改变我的方法。
  • 好的,现在看下面 Kdoto 的答案;我现在确信最坏情况下的界​​限可能是不可能的。所以也许谷歌人一定在寻找 Amortized O(1)。编辑:好的,作为 Kdoto 答案的 cmets 中的 templatetypedef 指针,证明不正确。已注明。
  • 别那么肯定,我的证明不正确。但是,我认为没有为所有操作找到 O(1),无论是否摊销。我怀疑这是不可能的。

标签: algorithm data-structures queue big-o


【解决方案1】:

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

那你可以use two stacks to implement the queue。压入一个堆栈,从另一个堆栈弹出;如果在弹出期间第二个堆栈为空,则将所有元素从第一个堆栈移动到第二个。

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

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

它将有 O(1) get_min()push() 和摊销 O(1) pop()

【讨论】:

  • 使用两个栈来实现队列如何给你摊销 O(1) pop?
  • @template 每个元素只能从一个堆栈移动到另一个堆栈一次。
  • 如果你将“当前最小值与元素一起存储”,然后从队列中弹出最小值,你怎么知道新的最小值是多少,在 O(1) 时间内?
  • @adamax 我听不懂第三部分。你的最低限度是如何工作的。如您所见,这里的 cmets 太多了。为什么不提供一个小例子,包括你的算法步骤。这将有助于理解您的算法。
  • @adamax 我终于明白你的解决方案了。您应该补充说明,当我们将元素从第一个结构移动到第二个结构时,我们应该重新计算第二个元素的值。顺便说一句,正如我在回答中所展示的那样,可以在 o(1) 中而不是在摊销的 O(1) 中执行所有这些操作。 :)
【解决方案2】:

好的 - 我想我有一个答案,可以在 amortized O(1) 中为您提供所有这些操作,这意味着任何一个操作都可能占用 O(n),但任何序列的n 次操作需要 O(1) 时间每次操作

我们的想法是将您的数据存储为Cartesian tree。这是一棵遵循最小堆属性(每个节点不大于其子​​节点)的二叉树,并且其排序方式使得节点的中序遍历以与添加节点相同的顺序返回节点。例如,这是序列2 1 4 3 5 的笛卡尔树:

       1
     /   \
    2      3
          / \
         4   5

可以使用以下过程在 O(1) 摊销时间内将元素插入到笛卡尔树中。看看树的右脊(从根到最右边叶子的路径,总是向右走形成的)。从最右边的节点开始,沿着这条路径向上扫描,直到找到比您要插入的节点小的第一个节点。
更改该节点,使其右孩子成为这个新节点,然后使该节点的前右孩子成为您刚刚添加的节点的左孩子。例如,假设我们想在上面的树中插入另一个 2 的副本。我们沿着右侧脊椎向上走,经过 5 和 3,但在 1 下方停下,因为​​ 1

       1
     /   \
    2      2
          /
         3
        / \
       4   5

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

这在摊销 O(1) 中运行,因为我们可以创建一个势函数,该函数等于树右脊柱中的节点数。插入节点所需的实际时间是 1 加上我们考虑的脊柱中的节点数(称为 k)。一旦我们找到插入节点的位置,spine 的大小就会缩小 k - 1 的长度,因为我们访问的 k 个节点中的每一个都不再位于正确的 spin 上,而新节点在它的位置。对于已摊销的 O(1) 插入,这给出了 1 + k + (1 - k) = 2 = O(1) 的摊销成本。作为另一种思考方式,一旦一个节点从右脊椎移开,它就不再是右脊椎的一部分,因此我们永远不必再移动它。由于 n 个节点中的每个节点最多只能移动一次,这意味着 n 次插入最多可以进行 n 次移动,因此对于每个元素的摊销 O(1),总运行时间最多为 O(n)。

要执行出队步骤,我们只需从笛卡尔树中删除最左边的节点。如果这个节点是叶子,我们就完成了。否则,节点只能有一个孩子(右孩子),所以我们用它的右孩子替换节点。如果我们跟踪最左边的节点在哪里,这一步需要 O(1) 时间。但是,在删除最左边的节点并用它的右子节点替换它之后,我们可能不知道新的最左边节点在哪里。为了解决这个问题,我们只需从刚刚移动到最左边子节点的新节点开始沿着树的左脊椎向下走。我声称这仍然在 O(1) 摊销时间内运行。为了看到这一点,我声称在任何一次遍历中最多访问一次节点以找到最左边的节点。要看到这一点,请注意,一旦以这种方式访问​​了一个节点,我们可能需要再次查看它的唯一方法是将它从最左边节点的子节点移动到最左边节点。但是所有访问的节点都是最左边节点的父节点,所以这不可能发生。因此,在这个过程中每个节点最多被访问一次,并且pop在O(1)中运行。

我们可以在 O(1) 中找到最小值,因为笛卡尔树让我们可以免费访问树中的最小元素;它是树的根。

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

简而言之,我们得到 O(1) 分期的推送和弹出,以及 O(1) 最坏情况下的查找最小值。

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

【讨论】:

  • 我还在考虑你的流行音乐是否真的摊销了 O(1)。当我确认这一点时,我一定会赞成这个答案。如果其他人也帮助验证这个答案,那就太好了。
  • @Kdoto- 想一想,如果你想获得 O(1) 摊销出队,你需要每个节点存储一个父指针,因为这样当你删除一个叶子时你可以更新在最坏情况 O(1) 中指向树中最左边节点的指针。
  • 我看到你的实现keithschwarz.com/interesting/code/?dir=min-queue // 从队列中添加和删除非常清楚,但是发现两个新旧堆栈的最小值不清楚?对于 find min 你使用另一个结构?你能举个小例子吗?
【解决方案3】:

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

首先我们需要一些在 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 的前面元素,因为它等于 D 的前面元素 (5)。

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

最少是 7 个。:)

【讨论】:

  • 看来,如果你按下 2、3、1,那么 get_min 会返回 2 而不是 1。
  • @adamax 哎呀 :)。你得到了我。我更正了 push()。现在它工作正常,但不在 0(1) 中。它像你一样在摊销 O(1) 中工作:)。
  • @UmmaGumma,干得好!不过,小修正,当 12 从堆栈中弹出时,它的 5
【解决方案4】:

使用一个双端队列 (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。 A 有 n 个 push_back,B 有 n 个 push_back,B 最多有 n 个 pop_back,因为每个元素要么留在 B 中,要么从 B 中弹出一次。总共有 O(3n) 次操作,因此摊销成本为 O (1) 也适用于入队。

最后,这个算法起作用的原因是,当你将 x 排入 A 时,如果 B 中有大于 x 的元素,它们现在永远不会是最小值,因为 x 将比 B 中的任何元素在队列 A 中停留的时间更长(队列是 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])

【讨论】:

    【解决方案5】:

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

    这是假设 get_min() 不改变数据;如果您更喜欢 pop_min() 之类的东西(即删除最小元素),您可以简单地存储一个指向实际元素及其前面的元素(如果有)的指针,并使用 push_rear() 和 pop_front() 相应地更新它们也是。

    在 cmets 之后编辑:

    显然这会导致 O(n) 的 push 和 pop,前提是这些操作的最小更改,因此不严格满足要求。

    【讨论】:

    • 这不会给你一个 O(n) 弹出,因为你必须扫描所有元素才能找到新的最小值?
    • 我认为 get_min() 实际上并没有弹出数据。但是 pop_front() 确实会弹出数据。可以说前端节点也是最小节点,所以它被弹出了。现在我们如何才能在恒定时间内保持 min 属性?
    • 啊,好电话......虽然你是对的,@bits,但在你推送新的最小值或弹出当前最小值的情况下,它只是 O(n)。如果它必须是最坏情况的 O(1),我不知道这是可能的,但我很乐意看到其他情况。
    【解决方案6】:

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

    LinkedList 中的每个元素都属于类型

    class LinkedListElement
    {
       LinkedListElement next;
       int currentMin;
    }
    

    您可以有两个指针,一个指向开始,另一个指向结束。

    如果您将一个元素添加到队列的开头。检查 Start 指针和要插入的节点。如果 node to insert currentmin 小于 start currentmin node to insert currentmin 是最小值。否则用 start currentmin 更新 currentmin。

    对 enque 重复相同的操作。

    【讨论】:

      【解决方案7】:
      #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;
      }
      

      【讨论】:

      • 如果没有随附的、明确说明代码正确原因的说明,则发布代码是不好的。
      • 那段代码很容易解释。如果你想解释,你可以问而不是投反对票,好吗?
      • 我最喜欢 StackOverflow 的品质之一是这里的答案质量很高。当我访问其他网站时,似乎每个发帖的人都只是抛出一堆“不言自明的代码”,就像你的一样。不可避免地,其中一些是错误的,每一个都需要时间来理解和验证。一个好的答案会引导您完成验证过程,并抢先回答您可能遇到的问题。很难记得回来检查这些事情,所以我更喜欢投反对票,然后中立或投赞成票。
      • AFAICT,这和江来一个多月前已经给出的源代码和描述的算法是一样的。
      【解决方案8】:

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

      这是Python中的代码。队列是使用 List 实现的。
      主要思想在于 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
      

      【讨论】:

        【解决方案9】:

        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);
                }
            }
        }
        

        【讨论】:

          【解决方案10】:

          JavaScript 实现

          (感谢 adamax's solution 的想法;我大致基于它的实现。跳转到底部查看完整注释的代码或通读一般步骤请注意,这会在 O(1) 常数时间内找到最大值,而不是最小值——很容易改变):

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


          插入:O(1) 最坏情况

          对于enqueue,如果MaxQueue 为空,我们会将push 的值与元组 中的当前最大值一起设置为dqStack(相同的值,因为它是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,我们将从dqStack 中获取pop 并从元组返回值。

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

          然后,如果dqStack为空,则将eqStack中的所有值移动到dqStack,例如:

          // 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];
            }
          }

          【讨论】:

            【解决方案11】:

            我们知道 push 和 pop 是常数时间操作 [准确地说是 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;
                }
            

            【讨论】:

              猜你喜欢
              • 2020-12-25
              • 1970-01-01
              • 1970-01-01
              • 2014-08-12
              • 2015-07-06
              • 1970-01-01
              • 2014-08-22
              • 2015-04-15
              相关资源
              最近更新 更多