Implementieren Sie eine Warteschlange, in der push_rear (), pop_front () und get_min () konstante Zeitoperationen sind

76

Ich bin auf diese Frage gestoßen : Implementiere eine Warteschlange, in der push_rear (), pop_front () und get_min () konstante Zeitoperationen sind.

Ich dachte anfangs daran, eine Min-Heap-Datenstruktur zu verwenden, die O (1) -Komplexität für ein get_min () aufweist. Aber push_rear () und pop_front () wären O (log (n)).

Weiß jemand, wie man eine solche Warteschlange mit O (1) push (), pop () und min () am besten implementiert?

Ich habe darüber gegoogelt und wollte auf diesen Thread von Algorithm Geeks hinweisen . Es scheint jedoch, dass keine der Lösungen für alle drei Methoden der konstanten Zeitregel folgt: push (), pop () und min ().

Vielen Dank für alle Vorschläge.

Bits
quelle
Sind Sie mit amortisierten O (1) -Zeitgrenzen für all dies einverstanden, oder müssen dies Zeitgrenzen im ungünstigsten Fall sein?
Templatetypedef
Ich bin mir nicht sicher, ob es sich um eine Google-Interviewfrage handelt. Ich habe sie anfangs unter careercup.com/question?id=7263132 gesehen . Es scheint, dass die Frage Zeitbeschränkungen im schlimmsten Fall bedeutete. Scheint es unmöglich?
Bits
@ bits- Nein, es scheint definitiv möglich zu sein, und ich mache gerade mit. :-) Ich habe versucht, kartesische Bäume zu verwenden, um dies zu tun - dies gibt Ihnen O (1) amortisierte Einfügung und O (1) Suche, und ich habe fast O (1) amortisierte Löschung auch funktioniert. Aber wenn Sie nach Worst-Case-Grenzen suchen, werde ich meinen Ansatz ändern.
Templatetypedef
ok, jetzt schau dir Kdotos Antwort unten an; Ich bin mir jetzt sicher, dass Worst-Case-Grenzen möglicherweise nicht möglich sind. Vielleicht müssen Googler nach Amortized O (1) suchen. EDIT: ok, da templatetypedef in Kommentaren von Kdotos Antwort darauf hinweist, ist der Beweis nicht korrekt. Notiert.
Bits
Sei dir nicht so sicher, mein Beweis war nicht korrekt. Ich glaube jedoch nicht, dass O (1) für alle Operationen gefunden wurde, amortisiert oder nicht. Und ich vermute, dass es nicht möglich ist.
Olhovsky

Antworten:

99

Sie können einen Stapel mit O (1) pop (), push () und get_min () implementieren: Speichern Sie einfach das aktuelle Minimum zusammen mit jedem Element. So wird zum Beispiel der Stapel [4,2,5,1](1 oben) [(4,4), (2,2), (5,2), (1,1)].

Dann können Sie zwei Stapel verwenden, um die Warteschlange zu implementieren . Schieben Sie zu einem Stapel, Pop von einem anderen; Wenn der zweite Stapel während des Pops leer ist, verschieben Sie alle Elemente vom ersten Stapel zum zweiten.

Beispiel für eine popAnforderung, bei der alle Elemente vom ersten Stapel verschoben werden [(4,4), (2,2), (5,2), (1,1)], wäre der zweite Stapel [(1,1), (5,1), (2,1), (4,1)]. und jetzt das oberste Element vom zweiten Stapel zurückgeben.

Um das minimale Element der Warteschlange zu finden, schauen Sie sich die kleinsten zwei Elemente der einzelnen Min-Stapel an und nehmen Sie dann das Minimum dieser beiden Werte. (Natürlich gibt es hier eine zusätzliche Logik, falls einer der Stapel leer ist, aber das ist nicht zu schwer zu umgehen).

Es wird O (1) get_min()und push()O (1) amortisiert haben pop().

Adamax
quelle
10
Wie führt die Verwendung von zwei Stapeln zum Implementieren der Warteschlange zu amortisiertem O (1) -Pop?
Templatetypedef
7
@template Jedes Element kann nur einmal von einem Stapel auf einen anderen verschoben werden.
Adamax
6
Wenn Sie das "aktuelle Minimum zusammen mit den Elementen" speichern und das Minimum aus der Warteschlange entfernen, wie würden Sie wissen, wie hoch das neue Minimum in O (1) ist?
Olhovsky
4
@adamax Ich kann den 3. Teil nicht verstehen. Wie dein Minimum funktioniert. Wie Sie sehen, gibt es hier zu viele Kommentare. Warum nicht einfach ein kleines Beispiel mit Ihren Algorithmusschritten geben? Es wird Ihnen helfen, Ihren Algorithmus zu verstehen.
UmmaGumma
7
@adamax Ich verstehe endlich deine Lösung. Sie sollten Ihrer Erklärung hinzufügen, dass wir die Werte der zweiten Elemente neu berechnen sollten, wenn wir Elemente von der ersten zur zweiten Struktur verschieben. Übrigens, wie ich in meiner Antwort zeige Es ist möglich, alle diese Operationen in o (1) und nicht in amortisiertem O (1) durchzuführen. :)
UmmaGumma
28

Okay - ich glaube, ich habe eine Antwort, die Ihnen alle diese Operationen in amortisiertem O (1) gibt, was bedeutet, dass jede Operation bis zu O (n) dauern kann, aber jede Folge von n Operationen O (1) Zeit pro Operation benötigt .

Die Idee ist, Ihre Daten als kartesischen Baum zu speichern . Dies ist ein Binärbaum, der der Eigenschaft min-heap gehorcht (jeder Knoten ist nicht größer als seine untergeordneten Knoten) und so angeordnet ist, dass Sie bei einer fehlerhaften Durchquerung der Knoten die Knoten in der Reihenfolge zurückgeben, in der sie hinzugefügt wurden. Hier ist zum Beispiel ein kartesischer Baum für die Sequenz 2 1 4 3 5:

       1
     /   \
    2      3
          / \
         4   5

Es ist möglich, ein Element in O (1) amortisierter Zeit in einen kartesischen Baum einzufügen, indem das folgende Verfahren angewendet wird. Schauen Sie sich den rechten Rücken des Baumes an (den Weg von der Wurzel zum Blatt ganz rechts, der gebildet wird, indem Sie immer nach rechts gehen). Beginnen Sie am Knoten ganz rechts und scannen Sie auf diesem Pfad nach oben, bis Sie den ersten Knoten finden, der kleiner als der Knoten ist, den Sie einfügen.
Ändern Sie diesen Knoten so, dass sein rechtes Kind dieser neue Knoten ist, und machen Sie dann das frühere rechte Kind dieses Knotens zum linken Kind des Knotens, den Sie gerade hinzugefügt haben. Angenommen, wir möchten eine weitere Kopie von 2 in den obigen Baum einfügen. Wir gehen den rechten Rücken entlang an den 5 und 3 vorbei, halten aber unterhalb der 1 an, weil 1 <2. Dann ändern wir den Baum so, dass er so aussieht:

       1
     /   \
    2      2
          /
         3
        / \
       4   5

Beachten Sie, dass eine Inorder Traversal 2 1 4 3 5 2 ergibt. Dies ist die Reihenfolge, in der wir die Werte hinzugefügt haben.

Dies läuft in amortisiertem O (1) ab, da wir eine potenzielle Funktion erstellen können, die der Anzahl der Knoten im rechten Rücken des Baums entspricht. Die zum Einfügen eines Knotens erforderliche Echtzeit beträgt 1 plus der Anzahl der Knoten in der Wirbelsäule, die wir berücksichtigen (nennen Sie dies k). Sobald wir den Platz zum Einfügen des Knotens gefunden haben, verkleinert sich die Größe der Wirbelsäule um die Länge k - 1, da sich jeder der k Knoten, die wir besucht haben, nicht mehr auf der rechten Wirbelsäule befindet und der neue Knoten an seiner Stelle ist. Dies ergibt amortisierte Kosten von 1 + k + (1 - k) = 2 = O (1) für den amortisierten O (1) -Einsatz. Als eine andere Art, darüber nachzudenken, ist ein Knoten, der einmal von der rechten Wirbelsäule entfernt wurde, nie wieder Teil der rechten Wirbelsäule, und wir werden ihn nie wieder bewegen müssen. Da jeder der n Knoten höchstens einmal verschoben werden kann, bedeutet dies, dass n Einfügungen höchstens n Bewegungen ausführen können.

Um einen Warteschlangenschritt auszuführen, entfernen wir einfach den Knoten ganz links aus dem kartesischen Baum. Wenn dieser Knoten ein Blatt ist, sind wir fertig. Andernfalls kann der Knoten nur ein Kind haben (das richtige Kind), und so ersetzen wir den Knoten durch sein rechtes Kind. Vorausgesetzt, wir verfolgen, wo sich der Knoten ganz links befindet, benötigt dieser Schritt O (1) Zeit. Nachdem Sie den Knoten ganz links entfernt und durch sein rechtes untergeordnetes Element ersetzt haben, wissen wir möglicherweise nicht, wo sich der neue Knoten ganz links befindet. Um dies zu beheben, gehen wir einfach den linken Rücken des Baumes entlang, beginnend mit dem neuen Knoten, den wir gerade zum Kind ganz links verschoben haben. Ich behaupte, dass dies immer noch in O (1) amortisierter Zeit läuft. Um dies zu sehen, behaupte ich, dass ein Knoten während eines dieser Durchgänge höchstens einmal besucht wird, um den Knoten ganz links zu finden. Beachten Sie, dass nach dem Besuch eines Knotens auf diese Weise Die einzige Möglichkeit, die wir jemals wieder betrachten könnten, wäre, wenn sie von einem untergeordneten Element des Knotens ganz links auf den Knoten ganz links verschoben würde. Alle besuchten Knoten sind jedoch Eltern des Knotens ganz links, sodass dies nicht passieren kann. Folglich wird jeder Knoten während dieses Vorgangs höchstens einmal besucht, und der Pop wird in O (1) ausgeführt.

Wir können find-min in O (1) machen, weil der kartesische Baum uns freien Zugang zum kleinsten Element des Baumes gibt; Es ist die Wurzel des Baumes.

Um zu sehen, dass die Knoten in der Reihenfolge zurückkehren, in der sie eingefügt wurden, beachten Sie, dass ein kartesischer Baum seine Elemente immer so speichert, dass eine Inorder-Traversierung sie in sortierter Reihenfolge besucht. Da wir bei jedem Schritt immer den Knoten ganz links entfernen und dies das erste Element der Inorder Traversal ist, erhalten wir die Knoten immer in der Reihenfolge zurück, in der sie eingefügt wurden.

Kurz gesagt, wir erhalten O (1) amortisiertes Push and Pop und O (1) Worst-Case-Find-Min.

Wenn ich eine O (1) -Implementierung im schlimmsten Fall finden kann, werde ich sie definitiv veröffentlichen. Das war ein großes Problem; danke fürs posten!

templatetypedef
quelle
Ich überlege immer noch, ob dein Pop wirklich amortisiert ist O (1). Ich werde diese Antwort auf jeden Fall positiv bewerten, wenn ich dies bestätige. Es wäre schön, wenn jemand anderes helfen würde, diese Antwort ebenfalls zu überprüfen.
Olhovsky
@ Kdoto- Wenn Sie sich das vorstellen, muss jeder Knoten einen übergeordneten Zeiger speichern, wenn Sie die amortisierte O (1) -Dequeue erhalten möchten. Wenn Sie auf diese Weise ein Blatt entfernen, können Sie den Zeiger auf den Knoten ganz links im Knoten aktualisieren Baum im schlimmsten Fall O (1).
Templatetypedef
Ich sehe Ihre Implementierung keithschwarz.com/interesting/code/?dir=min-queue // Hinzufügen und Löschen aus der Warteschlange ist sehr klar, aber finden Sie min nicht klar mit zwei alten und neuen Stapel? für find min verwenden Sie eine andere Struktur? Würden Sie bitte ein kleines Beispiel geben, wie es funktioniert?
Mio Unio
23

Ok, hier ist eine Lösung.

Zuerst brauchen wir einige Dinge, die push_back (), push_front (), pop_back () und pop_front () in 0 (1) bereitstellen. Es ist einfach mit Array und 2 Iteratoren zu implementieren. Der erste Iterator zeigt nach vorne, der zweite nach hinten. Nennen wir solche Sachen deque.

Hier ist Pseudocode:

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

Erläuterung:

Beispiel: Lassen Sie uns Zahlen [12,5,10,7,11,19] und in unsere MyQueue übertragen

1) Drücken von 12

D [12]
Min[12]

2) Drücken von 5

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

3) Drücken von 10

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

4) Drücken von 7

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

6) Drücken von 11

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

7) Drücken von 19

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

Rufen wir jetzt pop_front () auf

wir haben

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

Das Minimum ist 5

Rufen wir noch einmal pop_front () auf

Erläuterung: pop_front entfernt 5 aus D, aber auch das vordere Element von Min, da es dem vorderen Element von D (5) entspricht.

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

Und das Minimum ist 7. :)

UmmaGumma
quelle
Es scheint, dass, wenn Sie 2, 3, 1 drücken, get_min 2 statt 1
zurückgibt.
@adamax Ups :). Du hast mich. Ich habe push () korrigiert. Jetzt funktioniert es richtig, aber nicht in 0 (1). Es funktioniert in amortisiertem O (1) wie deins :).
UmmaGumma
2
@ UmmaGumma, gute Arbeit! Kleinere Korrektur, seine 5 <12, wenn 12 vom Stapel genommen wird.
Sucher
3

Verwenden Sie eine Deque (A) zum Speichern der Elemente und eine andere Deque (B) zum Speichern der Minima.

Wenn x in die Warteschlange gestellt ist, drücken Sie es zurück auf A und setzen Sie das Zurücksetzen von B fort, bis die Rückseite von B kleiner als x ist, und drücken Sie dann x auf B zurück.

Wenn Sie A aus der Warteschlange entfernen, wird pop_front A als Rückgabewert verwendet. Wenn es gleich der Vorderseite von B ist, wird pop_front B ebenfalls verwendet.

Wenn Sie das Minimum von A erhalten, verwenden Sie die Vorderseite von B als Rückgabewert.

Dequeue und Getmin sind offensichtlich O (1). Berücksichtigen Sie für die Enqueue-Operation den push_back von n Elementen. Es gibt n Push_back zu A, n Push_back zu B und höchstens n Pop_back von B, da jedes Element entweder in B bleibt oder einmal aus B herausspringt. Insgesamt gibt es O (3n) -Operationen und daher betragen die fortgeführten Anschaffungskosten O. (1) auch für die Warteschlange.

Der Grund, warum dieser Algorithmus funktioniert, ist, dass wenn Sie x in A einreihen und Elemente in B größer als x sind, diese jetzt niemals minimal sind, da x länger in der Warteschlange A bleibt als alle Elemente in B (eine Warteschlange) ist FIFO). Daher müssen wir Elemente in B (von hinten) herausspringen lassen, die größer als x sind, bevor wir x in B verschieben.

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])
Jianglai
quelle
1

Wenn es Ihnen nichts ausmacht, ein paar zusätzliche Daten zu speichern, sollte es trivial sein, den Mindestwert zu speichern. Push and Pop kann den Wert aktualisieren, wenn das neue oder entfernte Element das Minimum ist, und die Rückgabe des Minimalwerts ist so einfach wie das Abrufen des Werts der Variablen.

Dies setzt voraus, dass get_min () die Daten nicht ändert; Wenn Sie lieber etwas wie pop_min () haben möchten (dh das minimale Element entfernen möchten), können Sie einfach einen Zeiger auf das tatsächliche Element und das vorhergehende Element (falls vorhanden) speichern und diese entsprechend mit push_rear () und pop_front () aktualisieren. auch.

Nach Kommentaren bearbeiten:

Offensichtlich führt dies zu O (n) Push and Pop für den Fall, dass sich das Minimum bei diesen Operationen ändert und somit die Anforderungen nicht strikt erfüllt.

Andy Mikula
quelle
1
Gibt Ihnen dies nicht einen O (n) Pop, da Sie alle Elemente scannen müssen, um die neue min zu finden?
Templatetypedef
2
Ich denke, get_min () lässt die Daten nicht wirklich platzen. Aber pop_front () öffnet die Daten. Nehmen wir an, der Frontknoten ist auch der Min-Knoten. Wie können wir nun die min-Eigenschaft in konstanter Zeit beibehalten?
Bits
Ah, guter Anruf ... obwohl Sie Recht haben, @bits, ist es nur O (n) für den Fall, dass Sie ein neues Minimum verschieben oder Ihr aktuelles Minimum erhöhen. Wenn es der schlimmste Fall O (1) sein muss, weiß ich nicht, dass es möglich ist, aber ich würde gerne etwas anderes sehen.
Andy Mikula
1

Lösungen für diese Frage, einschließlich Code, finden Sie hier: http://discuss.joelonsoftware.com/default.asp?interview.11.742223.32

Olhovsky
quelle
Links zu externen Seiten sind nicht hilfreich. Was ist, wenn die Verbindung unterbrochen wird? Außerdem: Die Seite, die Sie ebenfalls verlinken, ist nur eine lange Diskussion. Eine gute Antwort würde die wichtigen Elemente dieser Diskussion erfassen und den Flaum weglassen.
Richard
1

Sie können tatsächlich eine LinkedList verwenden, um die Warteschlange zu verwalten.

Jedes Element in LinkedList ist vom Typ

class LinkedListElement
{
   LinkedListElement next;
   int currentMin;
}

Sie können zwei Zeiger haben. Einer zeigt auf den Anfang und der andere auf das Ende.

Wenn Sie dem Anfang der Warteschlange ein Element hinzufügen. Untersuchen Sie den Startzeiger und den einzufügenden Knoten. Wenn der Knoten zum Einfügen von currentmin kleiner als der Start ist, ist der Knoten zum Einfügen von currentmin das Minimum. Andernfalls aktualisieren Sie currentmin mit start currentmin.

Wiederholen Sie das gleiche für Enque.

Sandeep
quelle
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;
}
Der Mann
quelle
Es ist nicht gut, eine Postleitzahl ohne eine begleitende, klar festgelegte Erklärung zu veröffentlichen, warum der Code richtig ist.
Richard
Dieser Code ist sehr selbsterklärend. Wenn Sie eine Erklärung wünschen, können Sie bitte fragen, anstatt abzustimmen.
TheMan
Eine der Eigenschaften, die mir an StackOverflow am besten gefällt, ist die hohe Qualität der Antworten hier. Wenn ich andere Websites besuche, scheint es, als würde jeder, der Beiträge veröffentlicht, nur Bündel von "selbsterklärendem Code" werfen, wie Ihre. Einige davon sind unweigerlich falsch und jeder braucht Zeit, um sie zu verstehen und zu überprüfen. Eine gute Antwort führt Sie durch den Überprüfungsprozess und beantwortet präventiv Fragen, die Sie möglicherweise haben. Es ist schwer, sich daran zu erinnern, zurück zu kommen und diese Dinge zu überprüfen, deshalb ziehe ich es vor, abzustimmen und dann zu neutralisieren oder abzustimmen.
Richard
AFAICT, dies ist derselbe Algorithmus wie der, der bereits als Quellcode angegeben und von jianglai mehr als einen Monat zuvor beschrieben wurde.
j_random_hacker
0

Diese Lösung enthält 2 Warteschlangen:
1. main_q - speichert die Eingabenummern.
2. min_q - speichert die min-Zahlen nach bestimmten Regeln, die wir beschrieben haben (erscheinen in den Funktionen MainQ.enqueue (x), MainQ.dequeue (), MainQ.get_min ()).

Hier ist der Code in Python. Die Warteschlange wird mithilfe einer Liste implementiert.
Die Hauptidee liegt in den Funktionen MainQ.enqueue (x), MainQ.dequeue (), MainQ.get_min ().
Eine wichtige Annahme ist, dass das Leeren einer Warteschlange o (0) dauert.
Am Ende wird ein Test bereitgestellt.

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

Die Ausgabe des obigen Tests ist:

"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
user3139774
quelle
0

Java-Implementierung

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);
        }
    }
}
Nitish Bhagat
quelle
0

JavaScript-Implementierung

( Dank an adamax 'Lösung für die Idee; ich habe eine Implementierung lose darauf aufgebaut. Springen Sie nach unten, um vollständig kommentierten Code zu sehen, oder lesen Sie die folgenden allgemeinen Schritte durch. Beachten Sie, dass dies den Maximalwert in O (1) konstanter Zeit anstelle von findet der Mindestwert - leicht zu ändern):

Die allgemeine Idee ist, beim Erstellen von zwei Stapel zu erstellen MaxQueue(ich habe eine verknüpfte Liste als zugrunde liegende StackDatenstruktur verwendet - nicht im Code enthalten; aber jeder Stackwird dies tun, solange er mit dem Einfügen / Löschen von O (1) implementiert ist). Eine werden wir meistens popvon ( dqStack) und eine werden wir meistens pushvon ( eqStack).


Einfügen: O (1) Worst Case

Denn enqueue, wenn die MaxQueueleer ist , werden wir pushder Wert dqStackzusammen mit dem aktuellen Maximalwert in einem Tupel (der gleiche Wert , da es der einzige Wert in der ist MaxQueue); z.B:

const m = new MaxQueue();

m.enqueue(6);

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

Wenn das MaxQueuenicht leer ist, geben wir pushnur den Wert an eqStack;

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

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

Aktualisieren Sie dann den Maximalwert im Tupel.

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


Streichung: O (1) abgeschrieben

Denn dequeuewir kommen popvon dqStackund geben den Wert vom Tupel zurück.

m.dequeue();
> 6

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

Wenn dann dqStackleer ist, bewegen sich alle Werte in eqStackzu dqStack, zum Beispiel:

// 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
*/

Wenn jeder Wert verschoben wird, prüfen wir, ob er größer als das Maximum ist , und speichern ihn in jedem Tupel:

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
*/

Da jeder Wert dqStack höchstens einmal verschoben wird , können wir sagen, dass dequeueO (1) die Zeitkomplexität amortisiert hat.


Finden des Maximalwerts: O (1) Worst Case

Dann können wir zu jedem Zeitpunkt aufrufen getMax, um den aktuellen Maximalwert in konstanter O (1) -Zeit abzurufen. Solange das MaxQueuenicht leer ist, kann der Maximalwert leicht aus dem nächsten Tupel herausgezogen werden 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
*/


Code

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

Scott Rüdiger
quelle
0

Wir wissen, dass Push und Pop konstante Zeitoperationen sind [O (1) um genau zu sein].

Wenn wir jedoch an get_min () denken [dh die aktuelle Mindestanzahl in der Warteschlange ermitteln], fällt uns im Allgemeinen als Erstes ein, dass die gesamte Warteschlange jedes Mal durchsucht wird, wenn die Anforderung nach dem Mindestelement erfolgt. Dies wird jedoch niemals den Betrieb mit konstanter Zeit ergeben, was das Hauptziel des Problems ist.

Dies wird in der Regel sehr häufig in den Interviews gefragt, daher müssen Sie den Trick kennen

Dazu müssen wir zwei weitere Warteschlangen verwenden, die den Überblick über das minimale Element behalten, und wir müssen diese beiden Warteschlangen weiter ändern, während wir Push- und Pop-Operationen in der Warteschlange ausführen, sodass das minimale Element in O (1) -Zeit erhalten wird .

Hier ist der selbstbeschreibende Pseudocode, der auf dem oben erwähnten Ansatz basiert.

    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;
    }
Sachin
quelle