Ich habe kürzlich an einem Interview teilgenommen, in dem ich gebeten wurde, "ein Programm zu schreiben, um 100 größte Zahlen aus einer Reihe von 1 Milliarde Zahlen zu finden".
Ich konnte nur eine Brute-Force-Lösung geben, die darin bestand, das Array in O (nlogn) -Zeitkomplexität zu sortieren und die letzten 100 Zahlen zu verwenden.
Arrays.sort(array);
Der Interviewer suchte nach einer besseren Zeitkomplexität. Ich versuchte ein paar andere Lösungen, antwortete ihm aber nicht. Gibt es eine bessere Lösung für die Zeitkomplexität?
O(1)
, da keine Dimensionserhöhung erfolgt. Der Interviewer hätte fragen sollen: "Wie finde ich m größte Elemente aus einem Array von n mit n >> m?".Antworten:
Sie können eine Prioritätswarteschlange mit den 100 größten Zahlen beibehalten und die Milliardenzahlen durchlaufen, wenn Sie auf eine Zahl stoßen, die größer als die kleinste Zahl in der Warteschlange (der Kopf der Warteschlange) ist. Entfernen Sie den Kopf der Warteschlange und fügen Sie die neue Zahl hinzu in die Warteschlange.
BEARBEITEN: Wie Dev bemerkte, ist bei einer mit einem Heap implementierten Prioritätswarteschlange die Komplexität des Einfügens in die Warteschlange komplex
O(logN)
Im schlimmsten Fall bekommt man was besser ist als
billionlog2(100)
billion
log2(billion)
Wenn Sie die größten K-Zahlen aus einer Menge von N Zahlen benötigen, ist die Komplexität im Allgemeinen
O(NlogK)
eher alsO(NlogN)
, dies kann sehr bedeutsam sein, wenn K im Vergleich zu N sehr klein ist.EDIT2:
Die erwartete Zeit dieses Algorithmus ist ziemlich interessant, da in jeder Iteration eine Einfügung auftreten kann oder nicht. Die Wahrscheinlichkeit, dass die i-te Zahl in die Warteschlange eingefügt wird, ist die Wahrscheinlichkeit, dass eine Zufallsvariable größer ist als mindestens
i-K
Zufallsvariablen aus derselben Verteilung (die ersten k Zahlen werden automatisch zur Warteschlange hinzugefügt). Wir können Auftragsstatistiken (siehe Link ) verwenden, um diese Wahrscheinlichkeit zu berechnen.{0, 1}
Nehmen wir zum Beispiel an, die Zahlen wurden zufällig gleichmäßig ausgewählt , der erwartete Wert der (iK) -ten Zahl (von i Zahlen) ist(i-k)/i
und die Wahrscheinlichkeit, dass eine Zufallsvariable größer als dieser Wert ist1-[(i-k)/i] = k/i
.Somit ist die erwartete Anzahl von Einfügungen:
Und die erwartete Laufzeit kann ausgedrückt werden als:
(
k
Zeit zum Generieren der Warteschlange mit den erstenk
Elementen, dannn-k
Vergleiche und die erwartete Anzahl von Einfügungen, wie oben beschrieben, dauert jeweils durchschnittlichlog(k)/2
)Beachten Sie, dass dieser Ausdruck , wenn er
N
im Vergleich zu sehr großK
ist, viel näher istn
alsNlogK
. Dies ist etwas intuitiv, da im Fall der Frage selbst nach 10000 Iterationen (was im Vergleich zu einer Milliarde sehr klein ist) die Wahrscheinlichkeit, dass eine Zahl in die Warteschlange eingefügt wird, sehr gering ist.quelle
k
konstant und klein zu betrachtenn
. Man sollte jedoch immer diese "normalen Umstände" berücksichtigen.Wenn dies in einem Interview gefragt wird, möchte der Interviewer wahrscheinlich Ihren Problemlösungsprozess sehen, nicht nur Ihr Wissen über Algorithmen.
Die Beschreibung ist recht allgemein gehalten. Vielleicht können Sie ihn nach dem Bereich oder der Bedeutung dieser Zahlen fragen, um das Problem zu verdeutlichen. Dies kann einen Interviewer beeindrucken. Wenn diese Zahlen beispielsweise für das Alter der Menschen in einem Land (z. B. China) stehen, ist dies ein viel einfacheres Problem. Mit der vernünftigen Annahme, dass niemand am Leben älter als 200 Jahre ist, können Sie ein int-Array der Größe 200 (möglicherweise 201) verwenden, um die Anzahl der Personen mit demselben Alter in nur einer Iteration zu zählen. Hier bedeutet der Index das Alter. Danach ist es ein Kinderspiel, die 100 größte Anzahl zu finden. Übrigens heißt dieses Algo Zählsortierung .
Wie auch immer, die Frage spezifischer und klarer zu machen, ist gut für Sie in einem Interview.
quelle
Sie können über die Zahlen iterieren, die O (n) annehmen.
Wenn Sie einen Wert finden, der größer als das aktuelle Minimum ist, fügen Sie den neuen Wert einer kreisförmigen Warteschlange mit der Größe 100 hinzu.
Das Minimum dieser kreisförmigen Warteschlange ist Ihr neuer Vergleichswert. Fügen Sie diese Warteschlange weiter hinzu. Wenn voll, extrahieren Sie das Minimum aus der Warteschlange.
quelle
Ich habe festgestellt, dass dies mit "Algorithmus" gekennzeichnet ist, aber einige andere Optionen wegwerfen wird, da es wahrscheinlich auch mit "Interview" gekennzeichnet sein sollte.
Woher stammen die 1 Milliarde Zahlen? Wenn es sich um eine Datenbank handelt, würde 'Wert aus Tabellenreihenfolge nach Wert absteigender Grenzwert 100 auswählen' die Aufgabe recht gut erfüllen - es kann Dialektunterschiede geben.
Ist das einmalig oder wird es wiederholt? Wenn wiederholt, wie oft? Wenn es sich um ein Einzelstück handelt und sich die Daten in einer Datei befinden, wird 'cat srcfile | sortieren (Optionen nach Bedarf) | Mit head -100 'erledigen Sie schnell produktive Arbeit, für die Sie bezahlt werden, während der Computer diese triviale Aufgabe erledigt.
Wenn es wiederholt wird, empfehlen wir Ihnen, einen angemessenen Ansatz zu wählen, um die erste Antwort zu erhalten und die Ergebnisse zu speichern / zwischenzuspeichern, damit Sie kontinuierlich die Top 100 melden können.
Schließlich gibt es diese Überlegung. Suchen Sie einen Einstiegsjob und ein Interview mit einem geekigen Manager oder zukünftigen Mitarbeiter? Wenn ja, können Sie alle Arten von Ansätzen herauswerfen, die die relativen technischen Vor- und Nachteile beschreiben. Wenn Sie nach einem eher leitenden Job suchen, gehen Sie wie ein Manager vor, der sich mit den Entwicklungs- und Wartungskosten der Lösung befasst, und sagen Sie "Vielen Dank" und gehen Sie, wenn sich der Interviewer auf CS-Trivia konzentrieren möchte . Es ist unwahrscheinlich, dass er und Sie dort viel Aufstiegspotenzial haben.
Viel Glück beim nächsten Interview.
quelle
Meine unmittelbare Reaktion darauf wäre die Verwendung eines Heaps, aber es gibt eine Möglichkeit, QuickSelect zu verwenden, ohne alle Eingabewerte gleichzeitig zur Hand zu haben.
Erstellen Sie ein Array der Größe 200 und füllen Sie es mit den ersten 200 Eingabewerten. Führen Sie QuickSelect aus und verwerfen Sie die niedrigen 100, sodass Sie 100 freie Plätze haben. Lesen Sie die nächsten 100 Eingabewerte ein und führen Sie QuickSelect erneut aus. Fahren Sie fort, bis Sie die gesamte Eingabe in Stapeln von 100 durchlaufen haben.
Am Ende haben Sie die Top 100 Werte. Für N Werte haben Sie QuickSelect ungefähr N / 100 Mal ausgeführt. Jede Quickselect kostet ungefähr das 200-fache einer Konstanten, sodass die Gesamtkosten das 2N-fache einer Konstanten betragen. Dies sieht für mich in der Größe der Eingabe linear aus, unabhängig von der Parametergröße, die ich in dieser Erklärung fest verdrahtet habe, um 100 zu sein.
quelle
partial_sort
direkt auf einem Datensatz von 200 Millionen 32-Bitint
(erstellt über ein MT19937, gleichmäßig verteilt).Ordering.greatestOf(Iterable, int)
. Es ist absolut linear und Single-Pass, und es ist ein super süßer Algorithmus. FWIW, wir haben auch einige tatsächliche Benchmarks: Seine konstanten Faktoren sind im Durchschnitt ein Haar langsamer als die herkömmliche Prioritätswarteschlange, aber diese Implementierung ist viel widerstandsfähiger gegen "Worst-Case" -Eingaben (z. B. streng aufsteigende Eingaben).Sie können den Schnellauswahlalgorithmus verwenden , um die Zahl im (nach Reihenfolge) Index [Milliarde-101] zu finden und dann über die Zahlen zu iterieren und die Zahlen zu finden, die von dieser Zahl abweichen.
Dieser Algorithmus Zeit ist: 2 XO (N) = O (N) (durchschnittliche Fallleistung)
Die zweite Option, wie sie Thomas Jungblut vorschlägt, ist:
Verwenden Sie Heap , um den MAX-Heap zu erstellen. Er nimmt O (N). Die obersten 100 Maximalzahlen befinden sich oben auf dem Heap. Sie müssen sie lediglich aus dem Heap entfernen (100 XO (Log (N)).
Dieser Algorithmus Zeit ist: O (N) + 100 XO (Log (N)) = O (N)
quelle
O(N)
Fall ist , ist das Ausführen von zwei QuickSelects und einem weiteren linearen Scan weitaus aufwändiger als erforderlich.100*O(N)
(wenn das eine gültige Syntax ist) =O(100*N)
=O(N)
(zugegebenermaßen können 100 variabel sein, wenn ja, ist dies nicht unbedingt wahr). Oh, und Quickselect hat die schlechteste Leistung von O (N ^ 2) (autsch). Und wenn es nicht in den Speicher passt, werden die Daten zweimal von der Festplatte neu geladen, was viel schlimmer als einmal ist (dies ist der Engpass).Obwohl die andere Quickselect-Lösung herabgestuft wurde, bleibt die Tatsache bestehen, dass Quickselect die Lösung schneller findet als die Verwendung einer Warteschlange der Größe 100. Quickselect hat in Bezug auf Vergleiche eine erwartete Laufzeit von 2n + o (n). Eine sehr einfache Implementierung wäre
Dies erfordert durchschnittlich 3n + o (n) Vergleiche. Darüber hinaus kann es effizienter gestaltet werden, indem bei der Schnellauswahl die 100 größten Elemente im Array an den 100 am weitesten rechts liegenden Stellen verbleiben. Tatsächlich kann die Laufzeit auf 2n + o (n) verbessert werden.
Es gibt das Problem, dass dies die erwartete Laufzeit ist und nicht der schlimmste Fall. Wenn Sie jedoch eine anständige Pivot-Auswahlstrategie verwenden (z. B. 21 Elemente zufällig auswählen und den Median dieser 21 als Pivot auswählen), kann die Anzahl der Vergleiche sein garantiert mit hoher Wahrscheinlichkeit höchstens (2 + c) n für eine beliebig kleine Konstante c.
Tatsächlich kann durch Verwendung einer optimierten Stichprobenstrategie (z. B. zufällige Auswahl von sqrt (n) -Elementen und Auswahl des 99. Perzentils) die Laufzeit für beliebig kleine c auf (1 + c) n + o (n) gesenkt werden (unter der Annahme, dass K die Anzahl der auszuwählenden Elemente o (n) ist).
Andererseits erfordert die Verwendung einer Warteschlange der Größe 100 O (log (100) n) -Vergleiche, und die Protokollbasis 2 von 100 ist ungefähr gleich 6,6.
Wenn wir dieses Problem im abstrakteren Sinne betrachten, indem wir die größten K-Elemente aus einem Array der Größe N auswählen, wobei K = o (N) ist, aber sowohl K als auch N unendlich sind, dann ist die Laufzeit der Schnellauswahlversion O (N) und die Warteschlangenversion sind O (N log K), daher ist die Schnellauswahl in diesem Sinne auch asymptotisch überlegen.
In Kommentaren wurde erwähnt, dass die Warteschlangenlösung in der erwarteten Zeit N + K log N bei einer zufälligen Eingabe ausgeführt wird. Natürlich ist die Annahme einer zufälligen Eingabe niemals gültig, es sei denn, die Frage gibt dies ausdrücklich an. Die Warteschlangenlösung könnte dazu dienen, das Array in zufälliger Reihenfolge zu durchlaufen. Dies verursacht jedoch die zusätzlichen Kosten für N Aufrufe an einen Zufallszahlengenerator sowie die Permutation des gesamten Eingabearrays oder die Zuweisung eines neuen Arrays der Länge N, das das enthält zufällige Indizes.
Wenn das Problem es Ihnen nicht erlaubt, sich in den Elementen des ursprünglichen Arrays zu bewegen, und die Kosten für die Zuweisung von Speicher hoch sind, ist das Duplizieren des Arrays keine Option, das ist eine andere Sache. Aber genau in Bezug auf die Laufzeit ist dies die beste Lösung.
quelle
Nehmen Sie die ersten 100 Zahlen der Milliarde und sortieren Sie sie. Jetzt einfach durch die Milliarde iterieren. Wenn die Quellennummer höher als die kleinste von 100 ist, in Sortierreihenfolge einfügen. Was Sie am Ende haben, ist etwas, das O (n) über die Größe des Sets viel näher kommt.
quelle
Zwei Optionen:
(1) Heap (priorityQueue)
Pflegen Sie einen Min-Heap mit einer Größe von 100. Durchlaufen Sie das Array. Wenn das Element kleiner als das erste Element im Heap ist, ersetzen Sie es.
(2) Kartenreduzierungsmodell.
Dies ist dem Beispiel für die Wortanzahl in hadoop sehr ähnlich. Kartenjob: Zählen Sie die Häufigkeit oder die Zeiten jedes Elements. Reduzieren: Holen Sie sich das oberste K-Element.
Normalerweise würde ich dem Personalvermittler zwei Antworten geben. Gib ihnen was sie wollen. Natürlich wäre die Codierung zur Kartenreduzierung arbeitsintensiv, da Sie alle genauen Parameter kennen müssen. Kein Schaden, es zu üben. Viel Glück.
quelle
Eine sehr einfache Lösung wäre, das Array 100 Mal zu durchlaufen. Welches ist
O(n)
.Jedes Mal, wenn Sie die größte Zahl herausziehen (und ihren Wert auf den Mindestwert ändern, damit Sie ihn in der nächsten Iteration nicht sehen, oder die Indizes früherer Antworten verfolgen (indem Sie die Indizes verfolgen, die das ursprüngliche Array haben kann) Vielfaches derselben Zahl)). Nach 100 Iterationen haben Sie die 100 größten Zahlen.
quelle
Inspiriert von der Antwort von @ron teller, finden Sie hier ein Barebone-C-Programm, mit dem Sie tun können, was Sie wollen.
Auf meinem Computer (Core i3 mit einer schnellen SSD) dauert es 25 Sekunden und 1724 sortiert. Ich habe
dd if=/dev/urandom/ count=1000000000 bs=1
für diesen Lauf eine Binärdatei mit generiert .Offensichtlich gibt es Leistungsprobleme beim Lesen von jeweils nur 4 Bytes - von der Festplatte, aber dies ist zum Beispiel der Fall. Auf der positiven Seite wird sehr wenig Speicher benötigt.
quelle
Die einfachste Lösung besteht darin, das große Array mit Milliardenzahlen zu scannen und die 100 größten bisher gefundenen Werte in einem kleinen Array-Puffer ohne Sortierung zu speichern und sich den kleinsten Wert dieses Puffers zu merken. Zuerst dachte ich, dass diese Methode von fordprefect vorgeschlagen wurde, aber in einem Kommentar sagte er, dass er die 100-Zahlen-Datenstruktur als Heap implementierte. Immer wenn eine neue Zahl gefunden wird, die größer ist, wird das Minimum im Puffer durch den neu gefundenen Wert überschrieben und der Puffer erneut nach dem aktuellen Minimum durchsucht. Wenn die Zahlen im Milliarden-Zahlen-Array die meiste Zeit zufällig verteilt sind, wird der Wert aus dem großen Array mit dem Minimum des kleinen Arrays verglichen und verworfen. Nur für einen sehr sehr kleinen Bruchteil der Zahl muss der Wert in das kleine Array eingefügt werden. Daher kann der Unterschied bei der Manipulation der Datenstruktur, die die kleinen Zahlen enthält, vernachlässigt werden. Für eine kleine Anzahl von Elementen ist es schwierig festzustellen, ob die Verwendung einer Prioritätswarteschlange tatsächlich schneller ist als die Verwendung meines naiven Ansatzes.
Ich möchte die Anzahl der Einfügungen im kleinen 100-Element-Array-Puffer schätzen, wenn das 10 ^ 9-Element-Array gescannt wird. Das Programm scannt die ersten 1000 Elemente dieses großen Arrays und muss höchstens 1000 Elemente in den Puffer einfügen. Der Puffer enthält 100 Elemente der 1000 gescannten Elemente, dh 0,1 der gescannten Elemente. Wir nehmen also an, dass die Wahrscheinlichkeit, dass ein Wert aus dem großen Array größer als das aktuelle Minimum des Puffers ist, etwa 0,1 beträgt. Ein solches Element muss in den Puffer eingefügt werden. Jetzt scannt das Programm die nächsten 10 ^ 4 Elemente aus dem großen Array. Weil sich das Minimum des Puffers jedes Mal erhöht, wenn ein neues Element eingefügt wird. Wir haben geschätzt, dass das Verhältnis der Elemente, die größer als unser aktuelles Minimum sind, ungefähr 0,1 beträgt, und daher müssen 0,1 * 10 ^ 4 = 1000 Elemente eingefügt werden. Tatsächlich ist die erwartete Anzahl von Elementen, die in den Puffer eingefügt werden, kleiner. Nach dem Scannen dieser 10 ^ 4 Elemente beträgt der Bruchteil der Zahlen im Puffer etwa 0,01 der bisher gescannten Elemente. Wenn wir also die nächsten 10 ^ 5 Zahlen scannen, gehen wir davon aus, dass nicht mehr als 0,01 * 10 ^ 5 = 1000 in den Puffer eingefügt werden. In Fortsetzung dieser Argumentation haben wir nach dem Scannen von 1000 + 10 ^ 4 + 10 ^ 5 + ... + 10 ^ 9 ~ 10 ^ 9 Elementen des großen Arrays etwa 7000 Werte eingefügt. Wenn wir also ein Array mit 10 ^ 9 Elementen zufälliger Größe scannen, erwarten wir nicht mehr als 10 ^ 4 (= 7000 aufgerundete) Einfügungen in den Puffer. Nach jedem Einfügen in den Puffer muss das neue Minimum gefunden werden. Wenn der Puffer ein einfaches Array ist, benötigen wir einen Vergleich von 100, um das neue Minimum zu finden. Wenn der Puffer eine andere Datenstruktur ist (wie ein Heap), benötigen wir mindestens einen Vergleich, um das Minimum zu finden. Um die Elemente des großen Arrays zu vergleichen, benötigen wir 10 ^ 9 Vergleiche. Alles in allem benötigen wir also ungefähr 10 ^ 9 + 100 * 10 ^ 4 = 1,001 * 10 ^ 9 Vergleiche, wenn wir ein Array als Puffer verwenden, und mindestens 1.000 * 10 ^ 9 Vergleiche, wenn wir eine andere Art von Datenstruktur verwenden (wie einen Heap). . Die Verwendung eines Heaps bringt also nur einen Gewinn von 0,1%, wenn die Leistung durch die Anzahl der Vergleiche bestimmt wird. Aber was ist der Unterschied in der Ausführungszeit zwischen dem Einfügen eines Elements in einen 100-Element-Heap und dem Ersetzen eines Elements in einem 100-Element-Array und dem Finden seines neuen Minimums? 000 * 10 ^ 9 Vergleiche bei Verwendung einer anderen Art von Datenstruktur (wie ein Heap). Die Verwendung eines Heaps bringt also nur einen Gewinn von 0,1%, wenn die Leistung durch die Anzahl der Vergleiche bestimmt wird. Aber was ist der Unterschied in der Ausführungszeit zwischen dem Einfügen eines Elements in einen 100-Element-Heap und dem Ersetzen eines Elements in einem 100-Element-Array und dem Finden seines neuen Minimums? 000 * 10 ^ 9 Vergleiche bei Verwendung einer anderen Art von Datenstruktur (wie ein Heap). Die Verwendung eines Heaps bringt also nur einen Gewinn von 0,1%, wenn die Leistung durch die Anzahl der Vergleiche bestimmt wird. Aber was ist der Unterschied in der Ausführungszeit zwischen dem Einfügen eines Elements in einen 100-Element-Heap und dem Ersetzen eines Elements in einem 100-Element-Array und dem Finden seines neuen Minimums?
Auf theoretischer Ebene: Wie viele Vergleiche werden zum Einfügen in einen Heap benötigt? Ich weiß, dass es O (log (n)) ist, aber wie groß ist der konstante Faktor? ich
Auf Maschinenebene: Welche Auswirkungen haben Caching und Verzweigungsvorhersage auf die Ausführungszeit einer Heap-Einfügung und einer linearen Suche in einem Array?
Auf Implementierungsebene: Welche zusätzlichen Kosten sind in einer Heap-Datenstruktur verborgen, die von einer Bibliothek oder einem Compiler bereitgestellt wird?
Ich denke, dies sind einige der Fragen, die beantwortet werden müssen, bevor man versuchen kann, den tatsächlichen Unterschied zwischen der Leistung eines 100-Elemente-Heaps oder eines 100-Elemente-Arrays abzuschätzen. Es wäre also sinnvoll, ein Experiment durchzuführen und die tatsächliche Leistung zu messen.
quelle
Algorithmus Größte x-Elemente aus n:
Ich werde den Rückgabewert LIST aufrufen . Es ist eine Reihe von x Elementen (meiner Meinung nach sollte die Liste verknüpft werden)
Was ist der schlimmste Fall?
x log (x) + (nx) (log (x) +1) = nlog (x) + n - x
Das ist also O (n) Zeit für den schlimmsten Fall. Die +1 ist die Überprüfung, ob die Anzahl größer als die kleinste in LIST ist. Die erwartete Zeit für den Durchschnittsfall hängt von der mathematischen Verteilung dieser n Elemente ab.
Mögliche Verbesserungen
Dieser Algorithmus kann für das Worst-Case-Szenario leicht verbessert werden, aber IMHO (ich kann diese Behauptung nicht beweisen) wird das durchschnittliche Verhalten verschlechtern. Das asymptotische Verhalten wird dasselbe sein.
Die Verbesserung dieses Algorithmus besteht darin, dass wir nicht prüfen, ob das Element größer als das kleinste ist. Für jedes Element werden wir versuchen, es einzufügen, und wenn es kleiner als das kleinste ist, werden wir es ignorieren. Obwohl das absurd klingt, wenn wir nur das Worst-Case-Szenario betrachten, das wir haben werden
x log (x) + (nx) log (x) = nlog (x)
Operationen.
Für diesen Anwendungsfall sehe ich keine weiteren Verbesserungen. Sie müssen sich jedoch fragen: Was ist, wenn ich dies mehr als log (n) Mal und für verschiedene x-es tun muss? Offensichtlich würden wir dieses Array in O (n log (n)) sortieren und unser x-Element nehmen, wann immer wir es brauchen.
quelle
Diese Frage würde mit N log (100) Komplexität (anstelle von N log N) mit nur einer Zeile C ++ - Code beantwortet.
Die endgültige Antwort wäre ein Vektor, bei dem die ersten 100 Elemente garantiert die 100 größten Zahlen Ihres Arrays sind, während die verbleibenden Elemente ungeordnet sind
C ++ STL (Standardbibliothek) ist für diese Art von Problemen sehr praktisch.
Hinweis: Ich sage nicht, dass dies die optimale Lösung ist, aber es hätte Ihr Interview gespeichert.
quelle
Die einfache Lösung wäre, eine Prioritätswarteschlange zu verwenden, die ersten 100 Nummern zur Warteschlange hinzuzufügen und die kleinste Nummer in der Warteschlange zu verfolgen, dann die anderen Milliarden Nummern zu durchlaufen und jedes Mal eine zu finden, die größer als die größte Nummer ist In der Prioritätswarteschlange entfernen wir die kleinste Nummer, fügen die neue Nummer hinzu und verfolgen erneut die kleinste Nummer in der Warteschlange.
Wenn die Zahlen in zufälliger Reihenfolge wären, würde dies sehr gut funktionieren, da es beim Durchlaufen einer Milliarde Zufallszahlen sehr selten ist, dass die nächste Zahl zu den 100 größten gehört, die es bisher gab. Aber die Zahlen sind möglicherweise nicht zufällig. Wenn das Array bereits in aufsteigender Reihenfolge sortiert war, fügten wir immer ein Element in die Prioritätswarteschlange ein.
Also wählen wir zuerst 100.000 Zufallszahlen aus dem Array aus. Um einen langsamen Direktzugriff zu vermeiden, fügen wir beispielsweise 400 Zufallsgruppen mit 250 aufeinander folgenden Zahlen hinzu. Mit dieser zufälligen Auswahl können wir ziemlich sicher sein, dass nur sehr wenige der verbleibenden Zahlen in den Top 100 liegen, sodass die Ausführungszeit sehr nahe an der einer einfachen Schleife liegt, die eine Milliarde Zahlen mit einem Maximalwert vergleicht.
quelle
Das Finden der Top 100 aus einer Milliarde Zahlen erfolgt am besten mit einem Min-Heap von 100 Elementen.
Primen Sie zuerst den Min-Heap mit den ersten 100 gefundenen Zahlen. min-heap speichert die kleinste der ersten 100 Zahlen im Stammverzeichnis (oben).
Wenn Sie nun den Rest der Zahlen entlang gehen, vergleichen Sie sie nur mit der Wurzel (kleinste der 100).
Wenn die neu gefundene Nummer größer als die Wurzel von min-heap ist, ersetzen Sie die Wurzel durch diese Zahl, andernfalls ignorieren Sie sie.
Beim Einfügen der neuen Nummer in min-heap wird die kleinste Nummer im Heap an die Spitze (root) gesetzt.
Sobald wir alle Zahlen durchgegangen sind, haben wir die größten 100 Zahlen im Min-Heap.
quelle
Ich habe eine einfache Lösung in Python geschrieben, falls jemand interessiert ist. Es verwendet das
bisect
Modul und eine temporäre Rückgabeliste, die sortiert bleibt. Dies ähnelt einer Implementierung einer Prioritätswarteschlange.Verwendung mit 100.000.000 Elementen und Worst-Case-Eingabe, die eine sortierte Liste ist:
Es hat ungefähr 40 Sekunden gedauert, um dies für 100.000.000 Elemente zu berechnen, also habe ich Angst, es für 1 Milliarde zu tun. Um fair zu sein, habe ich ihm den Worst-Case-Input zugeführt (ironischerweise ein Array, das bereits sortiert ist).
quelle
Ich sehe viele O (N) -Diskussionen, daher schlage ich etwas anderes vor, nur für die Gedankenübung.
Gibt es bekannte Informationen über die Art dieser Zahlen? Wenn es zufälliger Natur ist, gehen Sie nicht weiter und schauen Sie sich die anderen Antworten an. Sie werden keine besseren Ergebnisse erzielen als sie.
Jedoch! Überprüfen Sie, ob der Listenfüllungsmechanismus diese Liste in einer bestimmten Reihenfolge gefüllt hat. Befinden sie sich in einem genau definierten Muster, in dem Sie mit Sicherheit wissen können, dass die größte Anzahl von Zahlen in einem bestimmten Bereich der Liste oder in einem bestimmten Intervall gefunden wird? Es kann ein Muster geben. Wenn dies der Fall ist, z. B. wenn garantiert wird, dass sie sich in einer Art Normalverteilung mit dem charakteristischen Buckel in der Mitte befinden, immer wieder aufwärts gerichtete Trends zwischen definierten Teilmengen aufweisen und zu einem bestimmten Zeitpunkt T in der Mitte der Daten eine verlängerte Spitze aufweisen Wenn Sie beispielsweise die Häufigkeit von Insidergeschäften oder Ausrüstungsfehlern festlegen oder einfach jede N-te Zahl wie bei der Analyse der Streitkräfte nach einer Katastrophe einen "Spike" aufweisen, können Sie die Anzahl der zu überprüfenden Datensätze erheblich reduzieren.
Es gibt sowieso einige Denkanstöße. Vielleicht hilft Ihnen dies, zukünftigen Interviewern eine nachdenkliche Antwort zu geben. Ich weiß, ich wäre beeindruckt, wenn mir jemand eine solche Frage als Antwort auf ein Problem wie dieses stellen würde - es würde mir sagen, dass er über Optimierung nachdenkt. Beachten Sie nur, dass es möglicherweise nicht immer eine Möglichkeit zur Optimierung gibt.
quelle
Erstellen Sie eine leere Liste mit 100 leeren Slots
Für jede Nummer in der Eingabeliste:
Wenn die Zahl kleiner als die erste ist, überspringen Sie
Andernfalls ersetzen Sie es durch diese Nummer
Schieben Sie dann die Nummer durch den benachbarten Swap. bis es kleiner als das nächste ist
Geben Sie die Liste zurück
Hinweis: Wenn dies der
log(input-list.size) + c < 100
Fall ist, besteht der optimale Weg darin, die Eingabeliste zu sortieren und die ersten 100 Elemente aufzuteilen.quelle
Die Komplexität ist O (N)
Erstellen Sie zunächst ein Array mit 100 Zoll. Initialisieren Sie das erste Element dieses Arrays als erstes Element der N-Werte. Verfolgen Sie den Index des aktuellen Elements mit einer anderen Variablen und nennen Sie es CurrentBig
Durchlaufen Sie die N-Werte
Wenn Sie fertig sind, drucken Sie das M-Array von CurrentBig 100 mal modulo 100 :-) Für den Schüler: Stellen Sie sicher, dass die letzte Zeile des Codes keine gültigen Daten übertrifft, bevor der Code beendet wird
quelle
Ein weiterer O (n) -Algorithmus -
Der Algorithmus findet die größten 100 durch Eliminierung
Betrachten Sie alle Millionen Zahlen in ihrer binären Darstellung. Beginnen Sie mit dem wichtigsten Punkt. Das Finden, ob das MSB 1 ist, kann durch eine Boolesche Operationsmultiplikation mit einer geeigneten Zahl erfolgen. Wenn diese Million mehr als 100 Einsen enthält, eliminieren Sie die anderen Zahlen mit Nullen. Von den verbleibenden Zahlen fahren Sie nun mit dem nächsthöheren Bit fort. Zählen Sie die Anzahl der verbleibenden Nummern nach der Eliminierung und fahren Sie fort, solange diese Anzahl größer als 100 ist.
Die Haupt-Boolesche Operation kann parallel zu GPUs ausgeführt werden
quelle
Ich würde herausfinden, wer die Zeit hatte, eine Milliarde Zahlen in ein Array zu stecken und ihn zu feuern. Muss für die Regierung arbeiten. Zumindest wenn Sie eine verknüpfte Liste hätten, könnten Sie eine Zahl in die Mitte einfügen, ohne eine halbe Milliarde zu bewegen, um Platz zu schaffen. Noch besser ermöglicht ein Btree eine binäre Suche. Jeder Vergleich eliminiert die Hälfte Ihrer Gesamtsumme. Ein Hash-Algorithmus würde es Ihnen ermöglichen, die Datenstruktur wie ein Schachbrett zu füllen, aber nicht so gut für spärliche Daten. Da es am besten ist, ein Lösungsarray mit 100 Ganzzahlen zu haben und die niedrigste Zahl in Ihrem Lösungsarray zu verfolgen, können Sie sie ersetzen, wenn Sie auf eine höhere Zahl im ursprünglichen Array stoßen. Sie müssten sich jedes Element im ursprünglichen Array ansehen, vorausgesetzt, es ist zunächst nicht sortiert.
quelle
Sie können es
O(n)
rechtzeitig tun . Durchlaufen Sie einfach die Liste und verfolgen Sie die 100 größten Zahlen, die Sie zu einem bestimmten Zeitpunkt gesehen haben, sowie den Mindestwert in dieser Gruppe. Wenn Sie eine neue Zahl finden, die größer ist als die kleinste Ihrer zehn, ersetzen Sie sie und aktualisieren Sie Ihren neuen Mindestwert von 100 (es kann eine konstante Zeit von 100 dauern, um dies jedes Mal zu bestimmen, dies hat jedoch keinen Einfluss auf die Gesamtanalyse ).quelle
Das Verwalten einer separaten Liste ist zusätzliche Arbeit und Sie müssen jedes Mal, wenn Sie einen anderen Ersatz finden, Dinge in der gesamten Liste verschieben. Sortieren Sie es einfach und nehmen Sie die Top 100.
quelle
Bitte beachten Sie esp. Der zweite Schritt könnte einfach parallel zu berechnen sein! Und es wird auch effizient sein, wenn Sie eine Million größter Elemente benötigen.
quelle
Dies ist eine Frage von Google oder anderen Branchenriesen. Möglicherweise ist der folgende Code die richtige Antwort, die von Ihrem Interviewer erwartet wird. Die Zeit- und Platzkosten hängen von der maximalen Anzahl im Eingabearray ab. Für 32-Bit-Int-Array-Eingaben betragen die maximalen Speicherkosten 4 * 125 MByte, die Zeitkosten 5 * Milliarden.
quelle
Ich habe meinen eigenen Code gemacht, nicht sicher, ob es das ist, wonach der "Interviewer" aussieht
quelle
Mögliche Verbesserungen.
Wenn die Datei 1 Milliarden Nummer enthält, kann das Lesen sehr lang sein ...
Um diese Arbeitsweise zu verbessern, können Sie:
quelle
Nehmen Sie zuerst 1000 Elemente und fügen Sie sie zu einem maximalen Haufen hinzu. Nehmen Sie nun die ersten maximal 100 Elemente heraus und speichern Sie sie irgendwo. Wählen Sie nun die nächsten 900 Elemente aus der Datei aus und fügen Sie sie zusammen mit den letzten 100 höchsten Elementen im Heap hinzu.
Wiederholen Sie diesen Vorgang, indem Sie 100 Elemente aus dem Heap aufnehmen und 900 Elemente aus der Datei hinzufügen.
Die endgültige Auswahl von 100 Elementen ergibt die maximalen 100 Elemente aus einer Milliarde Zahlen.
quelle
Problem: Finden Sie m größte Elemente von n Elementen, wobei n >>> m ist
Die einfachste Lösung, die für jeden offensichtlich sein sollte, besteht darin, einfach m Durchgänge des Blasensortierungsalgorithmus durchzuführen.
Drucken Sie dann die letzten n Elemente des Arrays aus.
Dies erfordert keine externen Datenstrukturen und verwendet einen Algorithmus, den jeder kennt.
Die geschätzte Laufzeit ist O (m * n). Die bisher besten Antworten sind O (n log (m)), daher ist diese Lösung für kleine m nicht wesentlich teurer.
Ich sage nicht, dass dies nicht verbessert werden könnte, aber dies ist bei weitem die einfachste Lösung.
quelle