it-swarm.dev

Holen Sie sich 100 höchste Zahlen aus einer unendlichen Liste

Einer meiner Freunde wurde diese Interviewfrage gestellt -

"Es gibt einen konstanten Zahlenfluss aus einer unendlichen Liste von Zahlen, aus denen Sie eine Datenstruktur pflegen müssen, um die 100 höchsten Zahlen zu einem bestimmten Zeitpunkt zurückzugeben. Angenommen, alle Zahlen sind nur ganze Zahlen."

Dies ist einfach. Sie müssen eine sortierte Liste in absteigender Reihenfolge führen und die niedrigste Nummer in dieser Liste verfolgen. Wenn die neu erhaltene Nummer größer als die niedrigste Nummer ist, müssen Sie diese niedrigste Nummer entfernen und die neue Nummer nach Bedarf in die sortierte Liste einfügen.

Dann wurde die Frage erweitert -

"Können Sie sicherstellen, dass die Reihenfolge der Einfügung O (1) ist? Ist es möglich?"

Soweit ich wusste, wäre es, selbst wenn Sie eine neue Nummer zur Liste hinzufügen und sie mit einem beliebigen Sortieralgorithmus erneut sortieren, am besten O(logn) für Quicksort (glaube ich). Also Mein Freund sagte, es sei nicht möglich. Aber er war nicht überzeugt, er bat darum, eine andere Datenstruktur als eine Liste beizubehalten.

Ich dachte an einen ausgeglichenen Binärbaum, aber selbst dort erhalten Sie die Einfügung nicht mit der Reihenfolge 1. Also die gleiche Frage, die ich jetzt auch habe. Wollte wissen, ob es eine solche Datenstruktur gibt, die das Einfügen in der Reihenfolge 1 für das oben genannte Problem durchführen kann oder überhaupt nicht möglich ist.

53
Sachin Shanbhag

Angenommen, k ist die Anzahl der höchsten Zahlen, die Sie kennen möchten (100 in Ihrem Beispiel). Dann können Sie in O(k) eine neue Nummer hinzufügen, die auch O(1) ist. Weil O(k*g) = O(g) if k is not zero and constant.

35
duedl0r

Halten Sie die Liste unsortiert. Das Herausfinden, ob eine neue Nummer eingefügt werden soll oder nicht, dauert länger, aber Einfügen ist O (1).

19

Das ist einfach. Die Größe der Konstantenliste, daher ist die Sortierzeit der Liste konstant. Eine Operation, die in konstanter Zeit ausgeführt wird, heißt O (1). Daher lautet die Sortierung der Liste O(1) für eine Liste mit fester Größe.

12
Kirk Broadhurst

Sobald Sie 100 Nummern übergeben haben, sind die maximalen Kosten, die Ihnen jemals für die nächste Nummer entstehen, die Kosten, um zu überprüfen, ob die Nummer die höchsten 100 Nummern enthält (nennen wir das CheckTime), zuzüglich der Kosten für die Eingabe es in diese Menge und wirf die niedrigste aus (nennen wir das EnterTime), was konstante Zeit ist (zumindest für begrenzte Zahlen), oder O (1).

Worst = CheckTime + EnterTime

Wenn die Verteilung der Zahlen zufällig ist, sinken die durchschnittlichen Kosten, je mehr Zahlen Sie haben. Zum Beispiel beträgt die Chance, dass Sie die 101. Nummer in den maximalen Satz eingeben müssen, 100/101, die Chancen für die 1000. Nummer wären 1/10 und die Chancen für die n-te Nummer wären 100/n. Unsere Gleichung für die Durchschnittskosten lautet also:

Average = CheckTime + EnterTime / n

Wenn sich n der Unendlichkeit nähert, ist nur CheckTime wichtig:

Average = CheckTime

Wenn die Zahlen gebunden sind, ist CheckTime konstant und somit O (1) Zeit.

Wenn die Zahlen nicht gebunden sind, wächst die Prüfzeit mit mehr Zahlen. Theoretisch liegt dies daran, dass Ihre Prüfzeit länger ist, wenn die kleinste Zahl im maximalen Satz groß genug wird, da Sie mehr Bits berücksichtigen müssen. Das lässt es scheinen, als wäre es etwas höher als die konstante Zeit. Sie könnten jedoch auch argumentieren, dass die Wahrscheinlichkeit, dass sich die nächste Zahl in der höchsten Menge befindet, gegen Null geht, wenn n gegen unendlich geht, und daher die Wahrscheinlichkeit, dass Sie mehr Bits berücksichtigen müssen, auch gegen 0 geht, was wäre ein Argument für O (1) Zeit.

Ich bin nicht positiv, aber mein Bauch sagt, dass es O (log (log (n)) Zeit ist. Dies liegt daran, dass die Wahrscheinlichkeit, dass die niedrigste Anzahl zunimmt, logarithmisch ist und die Wahrscheinlichkeit, dass die Anzahl der Bits, die Sie für jede Prüfung berücksichtigen müssen, ebenfalls logarithmisch ist. Ich interessiere mich für andere Leute, weil ich nicht wirklich sicher bin ...

9
Briguy37

dieser ist einfach, wenn Sie wissen Binary Heap Trees . Binäre Haufen unterstützen das Einfügen in die durchschnittliche konstante Zeit O (1). Und geben Ihnen einfachen Zugriff auf die ersten x Elemente.

7
Chris

Wenn der Interviewer mit der Frage wirklich die Frage stellen wollte, ob wir sicherstellen können, dass jede eingehende Nummer in konstanter Zeit verarbeitet wird, dann ist die Lösung Ihres Freundes bereits O (1), und, wie viele bereits darauf hingewiesen haben (siehe z. B. die Antwort von @ duedl0r) es wäre so, selbst wenn er eine unsortierte Liste oder eine Blasensortierung oder was auch immer verwendet hätte. In diesem Fall macht die Frage nicht viel Sinn, es sei denn, es war eine schwierige Frage oder Sie erinnern sich daran, dass sie falsch war.

Ich gehe davon aus, dass die Frage des Interviewers bedeutungsvoll war, dass er nicht gefragt hat, wie man etwas zu etwas macht O(1), was ganz offensichtlich schon das ist.

Da die Komplexität des Fragealgorithmus nur dann sinnvoll ist, wenn die Größe der Eingabe unbegrenzt wächst und die einzige Eingabe, die hier wachsen kann, 100 ist - die Listengröße; Ich gehe davon aus, dass die eigentliche Frage lautete: „Können wir sicherstellen, dass wir Top-N-Ausgaben erhalten? O(1) Zeit pro Nummer (nicht O(N) wie in Ihrer) Lösung des Freundes), ist es möglich? ”.

Das erste, was mir in den Sinn kommt, ist das Zählen der Sortierung, wodurch die Komplexität von O(1) Zeit pro Zahl für das Top-N-Problem für den Preis der Verwendung von O(m) Leerzeichen, wobei m die Länge des Bereichs eingehender Zahlen ist. Also ja, es ist möglich.

6
hamstergene

Verwenden Sie eine Warteschlange mit minimaler Priorität, die mit einem Fibonacci-Heap implementiert ist und eine konstante Einfügezeit hat:

1. Insert first 100 elements into PQ
2. loop forever
       n = getNextNumber();
       if n > PQ.findMin() then
           PQ.deleteMin()
           PQ.insert(n)
4
Gabe Moothart

Die Aufgabe besteht eindeutig darin, einen Algorithmus zu finden, der in der Länge N der erforderlichen Zahlenliste O(1)] ist. Es spielt also keine Rolle, ob Sie die Top-100-Zahl oder 10000-Zahlen benötigen sollte die Einfügezeit O (1) sein.

Der Trick dabei ist, dass, obwohl diese Anforderung O(1)] für die Listeneinfügung erwähnt wird, die Frage nichts über die Reihenfolge der Suchzeit im gesamten Zahlenraum aussagt, sondern sich dreht Dies kann auch gemacht werden O(1). Die Lösung lautet dann wie folgt:

  1. Ordnen Sie eine Hashtabelle mit Zahlen für Schlüssel und Paaren verknüpfter Listenzeiger für Werte an. Jedes Zeigerpaar ist der Anfang und das Ende einer verknüpften Listensequenz. Dies ist normalerweise nur ein Element und dann das nächste. Jedes Element in der verknüpften Liste steht neben dem Element mit der nächsthöheren Nummer. Die verknüpfte Liste enthält somit die sortierte Reihenfolge der erforderlichen Nummern. Führen Sie einen Datensatz mit der niedrigsten Nummer.

  2. Nimm eine neue Zahl x aus dem Zufallsstrom.

  3. Ist es höher als die zuletzt aufgezeichnete niedrigste Zahl? Ja => Schritt 4, Nein => Schritt 2

  4. Schlagen Sie die Hash-Tabelle mit der gerade genommenen Nummer. Gibt es einen Eintrag? Ja => Schritt 5. Nein => Nehmen Sie eine neue Zahl x-1 und wiederholen Sie diesen Schritt (dies ist eine einfache lineare Abwärtssuche, nehmen Sie mich hier mit, dies kann verbessert werden und ich werde erklären, wie)

  5. Fügen Sie mit dem gerade aus der Hash-Tabelle erhaltenen Listenelement die neue Nummer direkt nach dem Element in die verknüpfte Liste ein (und aktualisieren Sie den Hash).

  6. Nehmen Sie die niedrigste aufgezeichnete Zahl l (und entfernen Sie sie aus dem Hash/der Liste).

  7. Schlagen Sie die Hash-Tabelle mit der gerade genommenen Nummer. Gibt es einen Eintrag? Ja => Schritt 8. Nein => Nehmen Sie eine neue Zahl l + 1 und wiederholen Sie diesen Schritt (dies ist eine einfache lineare Suche nach oben).

  8. Bei einem positiven Treffer wird die Zahl zur neuen niedrigsten Zahl. Weiter zu Schritt 2

Um doppelte Werte zuzulassen, muss der Hash tatsächlich den Anfang und das Ende der verknüpften Listenfolge von Elementen beibehalten, die doppelte Werte sind. Durch Hinzufügen oder Entfernen eines Elements an einer bestimmten Taste wird der angezeigte Bereich vergrößert oder verkleinert.

Der Einsatz hier ist O (1). Die erwähnten Suchanfragen sind, denke ich, ungefähr O (durchschnittlicher Unterschied zwischen Zahlen). Die durchschnittliche Differenz nimmt mit der Größe des Zahlenraums zu, nimmt jedoch mit der erforderlichen Länge der Zahlenliste ab.

Die lineare Suchstrategie ist also ziemlich schlecht, wenn der Zahlenraum groß ist (z. B. für einen 4-Byte-Int-Typ 0 bis 2 ^ 32-1) und N = 100. Um dieses Leistungsproblem zu umgehen, können Sie parallele Sätze von Hashtabellen beibehalten, bei denen die Zahlen auf höhere Größen gerundet werden (z. B. 1s, 10s, 100s, 1000s), um geeignete Schlüssel zu erstellen. Auf diese Weise können Sie die Gänge hoch- und runterschalten, um die erforderlichen Suchvorgänge schneller durchzuführen. Die Leistung wird dann, glaube ich, zu einem O (log numberrange), das konstant ist, d. H. O(1) auch.

Stellen Sie sich zur Verdeutlichung vor, Sie hätten die Nummer 197 zur Hand. Sie treffen die 10er-Hash-Tabelle mit '190', sie wird auf die nächsten zehn gerundet. Etwas? Nein. Sie gehen also in 10 Sekunden nach unten, bis Sie sagen 120 sagen. Dann können Sie bei 129 in der 1s-Hashtabelle beginnen und dann 128, 127 versuchen, bis Sie etwas treffen. Sie haben jetzt in der verknüpften Liste gefunden, wo Sie die Nummer 197 einfügen können. Während Sie sie eingeben, müssen Sie auch die 1s-Hashtabelle mit dem Eintrag 197, die 10s-Hashtabelle mit der Nummer 190, 100s mit 100 usw. aktualisieren. Die meisten Schritte Sie müssen hier immer das 10-fache des Protokolls des Nummernkreises tun.

Ich habe vielleicht einige Details falsch verstanden, aber da dies der Austausch der Programmierer ist und der Kontext Interviews waren, würde ich hoffen, dass das oben Genannte eine überzeugende Antwort für diese Situation ist.

EDIT Ich habe hier einige zusätzliche Details hinzugefügt, um das parallele Hashtabellenschema zu erklären und wie es bedeutet, dass die von mir erwähnten schlechten linearen Suchvorgänge durch ein O(1) Suche. Ich habe auch festgestellt, dass es natürlich nicht erforderlich ist, nach der nächstniedrigeren Nummer zu suchen, da Sie direkt darauf zugreifen können, indem Sie in der Hashtabelle mit der niedrigsten Nummer nachsehen und zur nächsten übergehen Element.

2
Benedict

Hundert Zahlen können problemlos in einem Array der Größe 100 gespeichert werden. Jeder Baum, jede Liste oder jeder Satz ist angesichts der jeweiligen Aufgabe übertrieben.

Wenn die eingehende Nummer höher als die niedrigste (= letzte) im Array ist, führen Sie alle Einträge aus. Wenn Sie die erste gefunden haben, die kleiner als Ihre neue Nummer ist (Sie können dazu ausgefallene Suchanfragen verwenden), durchlaufen Sie den Rest des Arrays und drücken Sie jeden Eintrag um eins nach unten.

Da Sie die Liste von Anfang an sortiert halten, müssen Sie überhaupt keinen Sortieralgorithmus ausführen. Dies ist O (1).

1
Jörg Z.

Können wir annehmen, dass die Zahlen von einem festen Datentyp sind, wie z. B. Integer? Wenn ja, führen Sie eine Liste aller hinzugefügten Zahlen. Dies ist eine Operation O(1)].

  1. Deklarieren Sie ein Array mit so vielen Elementen wie möglich:
  2. Lesen Sie jede Nummer, während sie gestreamt wird.
  3. Zählen Sie die Nummer. Ignorieren Sie es, wenn diese Zahl bereits 100 Mal ermittelt wurde, da Sie sie nie benötigen werden. Dies verhindert, dass Überläufe unendlich oft gezählt werden.
  4. Wiederholen Sie ab Schritt 2.

VB.Net-Code:

Const Capacity As Integer = 100

Dim Tally(Integer.MaxValue) As Integer ' Assume all elements = 0
Do
    Value = ReadValue()
    If Tally(Value) < Capacity Then Tally(Value) += 1
Loop

Wenn Sie die Liste zurückgeben, können Sie so lange dauern, wie Sie möchten. Durchlaufen Sie einfach das Ende der Liste und erstellen Sie eine neue Liste mit den höchsten 100 aufgezeichneten Werten. Dies ist eine Operation O(n)], aber das ist irrelevant.

Dim List(Capacity) As Integer
Dim ListCount As Integer = 0
Dim Value As Integer = Tally.Length - 1
Dim ValueCount As Integer = 0
Do Until ListCount = List.Length OrElse Value < 0
    If Tally(Value) > ValueCount Then
        List(ListCount) = Value
        ValueCount += 1
        ListCount += 1
    Else
        Value -= 1
        ValueCount = 0
    End If
Loop
Return List

Edit : Tatsächlich spielt es keine Rolle, ob es sich um einen festen Datentyp handelt. Da der Speicherverbrauch (oder der Festplattenverbrauch) nicht begrenzt ist, können Sie dies für jeden Bereich positiver Ganzzahlen verwenden.

1
Hand-E-Food

Sie könnten einen binären Max-Heap verwenden. Sie müssten einen Zeiger auf den minimalen Knoten verfolgen (der unbekannt/null sein könnte).

Sie beginnen mit dem Einfügen der ersten 100 Zahlen in den Heap. Das Maximum wird oben sein. Danach behalten Sie immer 100 Nummern bei.

Wenn Sie dann eine neue Nummer erhalten:

if(minimumNode == null)
{
    minimumNode = findMinimumNode();
}
if(newNumber > minimumNode.Value)
{
    heap.Remove(minimumNode);
    minimumNode = null;
    heap.Insert(newNumber);
}

Leider ist findMinimumNode O (n), und diese Kosten fallen einmal pro Einfügung an (jedoch nicht während der Einfügung :). Das Entfernen des minimalen Knotens und das Einfügen des neuen Knotens sind im Durchschnitt O(1), da sie zum unteren Rand des Heaps tendieren.

Wenn Sie mit einem binären Min-Heap in die andere Richtung gehen, befindet sich die Min oben, was ideal ist, um die Min zum Vergleich zu finden. Sie ist jedoch zum Kotzen, wenn Sie das Minimum durch eine neue Zahl ersetzen müssen, die> min ist. Das liegt daran, dass Sie den min-Knoten entfernen müssen (immer O(logN)) und dann den neuen Knoten einfügen müssen (durchschnittliches O (1)). Sie haben also immer noch O(logN) das ist besser als Max-Heap, aber nicht O (1).

Wenn N konstant ist, haben Sie natürlich immer O (1). :) :)

0
Scott Whitlock