Schreiben Sie ein Programm, um 100 größte Zahlen aus einem Array von 1 Milliarde Zahlen zu finden


300

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?


70
Vielleicht ist das Problem, dass es keine Sortierfrage war , sondern eine Suchfrage .
Geomagas

11
Als technische Anmerkung ist Sortieren vielleicht nicht der beste Weg, um das Problem zu lösen, aber ich denke nicht, dass es rohe Gewalt ist - ich kann mir viel schlechtere Wege vorstellen, dies zu tun.
Bernhard Barker

88
Ich habe gerade an eine noch dümmere Brute-Force-Methode gedacht ... Finden Sie alle möglichen Kombinationen von 100 Elementen aus dem 1-Milliarden-Element-Array und sehen Sie, welche dieser Kombinationen die größte Summe hat.
Shashank

10
Beachten Sie, dass in diesem Fall alle deterministischen (und korrekten) Algorithmen verwendet werden 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?".
Bakuriu

Antworten:


328

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 komplexO(logN)

Im schlimmsten Fall bekommt man was besser ist alsbillionlog2(100)billionlog2(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 als O(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-KZufallsvariablen 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)/iund die Wahrscheinlichkeit, dass eine Zufallsvariable größer als dieser Wert ist 1-[(i-k)/i] = k/i.

Somit ist die erwartete Anzahl von Einfügungen:

Geben Sie hier die Bildbeschreibung ein

Und die erwartete Laufzeit kann ausgedrückt werden als:

Geben Sie hier die Bildbeschreibung ein

( kZeit zum Generieren der Warteschlange mit den ersten kElementen, dann n-kVergleiche und die erwartete Anzahl von Einfügungen, wie oben beschrieben, dauert jeweils durchschnittlich log(k)/2)

Beachten Sie, dass dieser Ausdruck , wenn er Nim Vergleich zu sehr groß Kist, viel näher ist nals NlogK. 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.


6
Es ist eigentlich nur O (100) für jeden Einsatz.
MrSmith42

8
@RonTeller Sie können eine verknüpfte Liste nicht effizient binär durchsuchen. Aus diesem Grund wird eine Prioritätswarteschlange normalerweise mit einem Heap implementiert. Ihre Einfügezeit wie beschrieben ist O (n), nicht O (logn). Sie hatten es beim ersten Mal richtig (geordnete Warteschlange oder Prioritätswarteschlange), bis Skizz Sie dazu brachte, sich selbst zu erraten.
Dev

17
@ ThomasJungblut Milliarde ist auch eine Konstante, also wenn das der Fall ist, ist es O (1): P
Ron Teller

9
@ RonTeller: Normalerweise geht es bei dieser Art von Fragen darum, 10 Top-Seiten aus Milliarden von Google-Suchergebnissen oder 50 häufigsten Wörter für eine Wortwolke oder 10 beliebtesten Songs auf MTV usw. zu finden. Ich glaube also, unter normalen Umständen Es ist sicher, im Vergleich zu k konstant und klein zu betrachten n. Man sollte jedoch immer diese "normalen Umstände" berücksichtigen.
Freund

5
Da Sie 1G-Elemente haben, probieren Sie 1000 Elemente nach dem Zufallsprinzip aus und wählen Sie die größten 100 aus. Dadurch sollten die entarteten Fälle (sortiert, umgekehrt sortiert, meistens sortiert) vermieden und die Anzahl der Einfügungen erheblich reduziert werden.
ChuckCottrill

136

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.


26
Sehr gute Punkte. Niemand sonst hat etwas über die Verteilung dieser Zahlen gefragt oder angegeben - es könnte den Unterschied in der Herangehensweise an das Problem ausmachen.
NealB

13
Ich möchte diese Antwort genug, um sie zu erweitern. Lesen Sie die Zahlen einmal durch, um die Min / Max-Werte zu erhalten, damit Sie von einer Verteilung ausgehen können. Nehmen Sie dann eine von zwei Möglichkeiten. Wenn der Bereich klein genug ist, erstellen Sie ein Array, in dem Sie Zahlen einfach abhaken können, sobald sie auftreten. Wenn der Bereich zu groß ist, verwenden Sie den oben beschriebenen sortierten Heap-Algorithmus. Nur ein Gedanke.
Richard_G

2
Ich stimme zu, dass es einen großen Unterschied macht, dem Interviewer eine Frage zu stellen. Tatsächlich kann eine Frage, wie z. B. ob Sie durch die Rechenleistung eingeschränkt sind oder nicht, Ihnen auch dabei helfen, die Lösung mithilfe mehrerer Rechenknoten zu parallelisieren.
Sumit Nigam

1
@R_G Sie müssen nicht die gesamte Liste durchgehen. Genug, um einen kleinen Teil (z. B. eine Million) zufälliger Mitglieder der Liste zu beproben, um nützliche Statistiken zu erhalten.
Itamar

Für diejenigen, die nicht über diese Lösung nachgedacht hätten, würde ich empfehlen, über die Zählsorte en.wikipedia.org/wiki/Counting_sort zu lesen . Das ist eigentlich eine ziemlich häufige Interviewfrage: Können Sie ein Array besser sortieren als O (nlogn)? Diese Frage ist nur eine Erweiterung.
Maxime Chéramy

69

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.


3
Das funktioniert nicht. Wenn Sie beispielsweise die Top 2 von {1, 100, 2, 99} finden, erhalten Sie {100,1} als Top 2.
Skizz

7
Sie können nicht herumkommen, um die Warteschlange sortiert zu halten. (wenn Sie nicht jedes Mal die
Lochwarteschlange

3
@ MrSmith42 Eine teilweise Sortierung wie in einem Haufen ist ausreichend. Siehe Ron Tellers Antwort.
Christopher Creutzig

1
Ja, ich habe stillschweigend angenommen, dass eine Extrakt-Min-Warteschlange als Heap implementiert ist.
Regenschein

Anstelle einer kreisförmigen Warteschlange wird mindestens ein Heap der Größe 100 verwendet. Oben stehen mindestens hundert Zahlen. Dies wird nur O (log n) für das Einfügen im Vergleich zu o (n) im Fall der Warteschlange
TechExplorer

33

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.


2
Außergewöhnliche Antwort. Alle anderen haben sich auf die technische Seite der Frage konzentriert, während diese Antwort den geschäftlichen sozialen Teil betrifft.
Vbocan

2
Ich hätte nie gedacht, dass Sie sich bedanken und ein Interview hinterlassen und nicht warten können, bis es fertig ist. Danke, dass du meinen Geist geöffnet hast.
UrsulRosu

1
Warum können wir nicht einen Haufen von Milliarden Elementen erstellen und 100 größte Elemente extrahieren? Auf diese Weise kosten = O (Milliarden) + 100 * O (log (Milliarden)) ??
Mohit Shah

17

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.


10
Sie können eine kleine, aber möglicherweise wichtige Optimierung hinzufügen: Nachdem Sie QuickSelect ausgeführt haben, um das Array der Größe 200 zu partitionieren, ist das Minimum der Top-100-Elemente bekannt. Füllen Sie dann beim Durchlaufen des gesamten Datensatzes die unteren 100 Werte nur auf, wenn der aktuelle Wert größer als das aktuelle Minimum ist. Eine einfache Implementierung dieses Algorithmus in C ++ entspricht der Ausführung von libstdc ++ partial_sortdirekt auf einem Datensatz von 200 Millionen 32-Bit int(erstellt über ein MT19937, gleichmäßig verteilt).
Dyp

1
Gute Idee - wirkt sich nicht auf die Worst-Case-Analyse aus, sieht aber gut aus.
Mcdowella

@mcdowella Es ist einen Versuch wert und ich werde es tun, danke!
Userx

8
Genau das macht Guava 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).
Louis Wasserman

15

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.

array={...the billion numbers...} 
result[100];

pivot=QuickSelect(array,billion-101);//O(N)

for(i=0;i<billion;i++)//O(N)
   if(array[i]>=pivot)
      result.add(array[i]);

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)


8
Sie arbeiten die gesamte Liste dreimal durch. 1 bio. Ganzzahlen sind ungefähr 4 GB. Was würden Sie tun, wenn Sie sie nicht in den Speicher einpassen können? Schnellauswahl ist in diesem Fall die schlechteste Wahl. Einmal iterieren und einen Haufen der 100 besten Elemente behalten, ist meiner Meinung nach die leistungsstärkste Lösung in O (n) (beachten Sie, dass Sie das O (log n) von Haufeneinsätzen abschneiden können, da n im Haufen 100 = konstant = sehr klein ist ).
Thomas Jungblut

3
Obwohl dies immer noch der O(N)Fall ist , ist das Ausführen von zwei QuickSelects und einem weiteren linearen Scan weitaus aufwändiger als erforderlich.
Kevin

Dies ist PSEUDO-Code. Alle Lösungen hier benötigen mehr Zeit (O (NLOG (N) oder 100 * O (N))
One Man Crew

1
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).
Bernhard Barker

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.
One Man Crew

10

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

array = input array of length n
r = Quickselect(array,n-100)
result = array of length 100
for(i = 1 to n)
  if(array[i]>r)
     add array[i] to result

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.


4
Ihr letzter Absatz ist der entscheidende Punkt: Mit einer Milliarde Zahlen ist es nicht möglich, alle Daten im Speicher zu halten oder Elemente auszutauschen. (Zumindest würde ich das Problem so interpretieren, da es sich um eine Interviewfrage handelte.)
Ted Hopp

14
Wenn bei einer algorithmischen Frage das Lesen der Daten ein Problem darstellt, muss dies in der Frage erwähnt werden. Die Frage lautet "gegebenes Array" nicht "gegebenes Array auf der Festplatte, das nicht in den Speicher passt und nicht nach dem von neuman-Modell manipuliert werden kann, das der Standard bei der Analyse von Algorithmen ist". In diesen Tagen können Sie einen Laptop mit 8 GB RAM bekommen. Ich bin mir nicht sicher, woher die Idee kommt, eine Milliarde Zahlen im Speicher zu halten. Ich habe gerade mehrere Milliarden Nummern auf meiner Workstation im Speicher.
Mrip

Zu Ihrer Information Die Worst-Case-Laufzeit von quickselect ist O (n ^ 2) (siehe en.wikipedia.org/wiki/Quickselect ) und ändert auch die Reihenfolge der Elemente im Eingabearray. Es ist möglich, eine O (n) -Lösung im ungünstigsten Fall mit einer sehr großen Konstante zu haben ( en.wikipedia.org/wiki/Median_of_medians ).
Punkte

Der schlimmste Fall einer Schnellauswahl ist exponentiell unwahrscheinlich, was bedeutet, dass dies für praktische Zwecke irrelevant ist. Es ist einfach, die Schnellauswahl so zu ändern, dass mit hoher Wahrscheinlichkeit die Anzahl der Vergleiche (2 + c) n + o (n) für beliebig kleine c beträgt.
Mrip

"Es bleibt die Tatsache, dass die Schnellauswahl die Lösung schneller findet als die Verwendung einer Warteschlange der Größe 100" - Nein. Die Heap-Lösung benötigt etwa N + Klog (N) -Vergleiche gegenüber dem 2N-Durchschnitt für die Schnellauswahl und 2,95 für den Median der Mediane. Es ist deutlich schneller für die gegebene K.
Neil G

5

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.


3
oops hat die detailliertere Antwort nicht gesehen als meine eigene.
Samuel Thurston

Nehmen Sie die ersten 500 oder so Zahlen und hören Sie erst auf zu sortieren (und werfen Sie die niedrigen 400 weg), wenn die Liste voll ist. (Und es versteht sich von selbst, dass Sie dann nur dann zur Liste hinzufügen, wenn die neue Nummer> die niedrigste unter den ausgewählten 100 ist.)
Hot Licks

4

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.

InSERT ELEMENT INTO HEAP: O(log100)
compare the first element: O(1)
There are n elements in the array, so the total would be O(nlog100), which is O(n)

(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.


+1 für MapReduce, ich kann nicht glauben, dass Sie der einzige waren, der Hadoop für eine Milliarde Zahlen erwähnte. Was wäre, wenn der Interviewer nach 1k Milliarden Zahlen fragen würde? Sie verdienen meiner Meinung nach mehr Stimmen.
Silviu Burcea

@ Silviu Burcea Vielen Dank. Ich schätze auch MapReduce. :)
Chris Su

Obwohl die Größe von 100 in diesem Beispiel konstant ist, sollten Sie dies wirklich auf eine separate Variable verallgemeinern, d. H. k. Da 100 so konstant wie 1 Milliarde ist, warum geben Sie der Größe des großen Satzes von Zahlen eine Größenvariable von n und nicht für den kleineren Satz von Zahlen? Eigentlich sollte Ihre Komplexität O (nlogk) sein, was nicht O (n) ist.
Tom Heard

1
Aber mein Punkt ist, wenn Sie nur die Frage beantworten, ist 1 Milliarde auch in der Frage festgelegt, warum also 1 Milliarde auf n und nicht 100 auf k verallgemeinern. Nach Ihrer Logik sollte die Komplexität tatsächlich O (1) sein, da in dieser Frage sowohl 1 Milliarde als auch 100 festgelegt sind.
Tom Heard

1
@ TomHeard Alles klar. O (nlogk) Es gibt nur einen Faktor, der die Ergebnisse beeinflusst. Dies bedeutet, wenn n immer größer wird, steigt das "Ergebnisniveau" linear an. Oder wir können sagen, dass ich selbst bei Billionen Zahlen immer noch 100 größte Zahlen bekommen kann. Sie können jedoch nicht sagen: Mit zunehmendem n nimmt das k zu, so dass das k das Ergebnis beeinflusst. Deshalb benutze ich O (nlogk), aber nicht O (nlogn)
Chris Su

4

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.


1
Zwei Nachteile - (1) Sie zerstören dabei die Eingabe - dies wird vorzugsweise vermieden. (2) Sie durchlaufen das Array mehrmals. Wenn das Array auf der Festplatte gespeichert ist und nicht in den Speicher passt, kann dies leicht fast 100-mal langsamer sein als die akzeptierte Antwort. (Ja, sie sind beide O (n), aber immer noch)
Bernhard Barker

Guter Anruf @Dukeling, ich habe zusätzliche Formulierungen hinzugefügt, um zu vermeiden, dass die ursprüngliche Eingabe geändert wird, indem die vorherigen Antwortindizes nachverfolgt werden. Welches wäre immer noch ziemlich einfach zu codieren.
James Oravec

Ein brillantes Beispiel für eine O (n) -Lösung, die viel langsamer als O (n log n) ist. log2 (1 Milliarde) ist nur 30 ...
gnasher729

@ gnasher729 Wie groß ist die in O (n log n) versteckte Konstante?
Wunder173

1

Inspiriert von der Antwort von @ron teller, finden Sie hier ein Barebone-C-Programm, mit dem Sie tun können, was Sie wollen.

#include <stdlib.h>
#include <stdio.h>

#define TOTAL_NUMBERS 1000000000
#define N_TOP_NUMBERS 100

int 
compare_function(const void *first, const void *second)
{
    int a = *((int *) first);
    int b = *((int *) second);
    if (a > b){
        return 1;
    }
    if (a < b){
        return -1;
    }
    return 0;
}

int 
main(int argc, char ** argv)
{
    if(argc != 2){
        printf("please supply a path to a binary file containing 1000000000"
               "integers of this machine's wordlength and endianness\n");
        exit(1);
    }
    FILE * f = fopen(argv[1], "r");
    if(!f){
        exit(1);
    }
    int top100[N_TOP_NUMBERS] = {0};
    int sorts = 0;
    for (int i = 0; i < TOTAL_NUMBERS; i++){
        int number;
        int ok;
        ok = fread(&number, sizeof(int), 1, f);
        if(!ok){
            printf("not enough numbers!\n");
            break;
        }
        if(number > top100[0]){
            sorts++;
            top100[0] = number;
            qsort(top100, N_TOP_NUMBERS, sizeof(int), compare_function);
        }

    }
    printf("%d sorts made\n"
    "the top 100 integers in %s are:\n",
    sorts, argv[1] );
    for (int i = 0; i < N_TOP_NUMBERS; i++){
        printf("%d\n", top100[i]);
    }
    fclose(f);
    exit(0);
}

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=1fü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.


1

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.


1
Das macht ein Haufen.
Neil G

@ Neil G: Was "das"?
Wunder173

1
Der obere Rand des Heaps ist das minimale Element im Heap, und neue Elemente werden mit einem Vergleich abgelehnt.
Neil G

1
Ich verstehe, was Sie sagen, aber selbst wenn Sie die absolute Anzahl von Vergleichen und nicht die asymptotische Anzahl von Vergleichen verwenden, ist das Array immer noch viel langsamer, da die Zeit zum "Einfügen eines neuen Elements, Verwerfen des alten Minimums und Finden eines neuen Minimums" beträgt 100 statt ungefähr 7.
Neil G

1
Okay, aber Ihre Schätzung ist sehr umständlich. Sie können die erwartete Anzahl von Einfügungen direkt mit k (digamma (n) - digamma (k)) berechnen, was kleiner als klog (n) ist. In jedem Fall geben sowohl der Heap als auch die Array-Lösung nur einen Vergleich aus, um ein Element zu verwerfen. Der einzige Unterschied besteht darin, dass die Anzahl der Vergleiche für ein eingefügtes Element 100 für Ihre Lösung beträgt, gegenüber bis zu 14 für den Heap (obwohl der durchschnittliche Fall wahrscheinlich viel geringer ist).
Neil G

1
 Although in this question we should search for top 100 numbers, I will 
 generalize things and write x. Still, I will treat x as constant value.

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)

  • Die ersten x Elemente werden "wie sie kommen" aus dem Pool entnommen und in LIST sortiert (dies erfolgt in konstanter Zeit, da x als konstante O (x log (x)) Zeit behandelt wird).
  • Für jedes Element, das als nächstes kommt, prüfen wir, ob es größer als das kleinste Element in LIST ist, und ob es das kleinste herausspringt und das aktuelle Element in LIST einfügt. Da es sich um eine geordnete Liste handelt, sollte jedes Element seinen Platz in der logarithmischen Zeit finden (binäre Suche), und da es geordnet ist, ist das Einfügen von Listen kein Problem. Jeder Schritt wird auch in konstanter Zeit (O (log (x)) Zeit) ausgeführt.

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.


1

Diese Frage würde mit N log (100) Komplexität (anstelle von N log N) mit nur einer Zeile C ++ - Code beantwortet.

 std::vector<int> myvector = ...; // Define your 1 billion numbers. 
                                 // Assumed integer just for concreteness 
 std::partial_sort (myvector.begin(), myvector.begin()+100, myvector.end());

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.


1

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.


1

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.


0

Ich habe eine einfache Lösung in Python geschrieben, falls jemand interessiert ist. Es verwendet das bisectModul und eine temporäre Rückgabeliste, die sortiert bleibt. Dies ähnelt einer Implementierung einer Prioritätswarteschlange.

import bisect

def kLargest(A, k):
    '''returns list of k largest integers in A'''
    ret = []
    for i, a in enumerate(A):
        # For first k elements, simply construct sorted temp list
        # It is treated similarly to a priority queue
        if i < k:
            bisect.insort(ret, a) # properly inserts a into sorted list ret
        # Iterate over rest of array
        # Replace and update return array when more optimal element is found
        else:
            if a > ret[0]:
                del ret[0] # pop min element off queue
                bisect.insort(ret, a) # properly inserts a into sorted list ret
    return ret

Verwendung mit 100.000.000 Elementen und Worst-Case-Eingabe, die eine sortierte Liste ist:

>>> from so import kLargest
>>> kLargest(range(100000000), 100)
[99999900, 99999901, 99999902, 99999903, 99999904, 99999905, 99999906, 99999907,
 99999908, 99999909, 99999910, 99999911, 99999912, 99999913, 99999914, 99999915,
 99999916, 99999917, 99999918, 99999919, 99999920, 99999921, 99999922, 99999923,
 99999924, 99999925, 99999926, 99999927, 99999928, 99999929, 99999930, 99999931,
 99999932, 99999933, 99999934, 99999935, 99999936, 99999937, 99999938, 99999939,
 99999940, 99999941, 99999942, 99999943, 99999944, 99999945, 99999946, 99999947,
 99999948, 99999949, 99999950, 99999951, 99999952, 99999953, 99999954, 99999955,
 99999956, 99999957, 99999958, 99999959, 99999960, 99999961, 99999962, 99999963,
 99999964, 99999965, 99999966, 99999967, 99999968, 99999969, 99999970, 99999971,
 99999972, 99999973, 99999974, 99999975, 99999976, 99999977, 99999978, 99999979,
 99999980, 99999981, 99999982, 99999983, 99999984, 99999985, 99999986, 99999987,
 99999988, 99999989, 99999990, 99999991, 99999992, 99999993, 99999994, 99999995,
 99999996, 99999997, 99999998, 99999999]

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


0

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.


0
Time ~ O(100 * N)
Space ~ O(100 + N)
  1. Erstellen Sie eine leere Liste mit 100 leeren Slots

  2. 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

  3. Geben Sie die Liste zurück


Hinweis: Wenn dies der log(input-list.size) + c < 100Fall ist, besteht der optimale Weg darin, die Eingabeliste zu sortieren und die ersten 100 Elemente aufzuteilen.


0

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

if N[i] > M[CurrentBig] {

M[CurrentBig]=N[i]; ( overwrite the current value with the newly found larger number)

CurrentBig++;      ( go to the next position in the M array)

CurrentBig %= 100; ( modulo arithmetic saves you from using lists/hashes etc.)

M[CurrentBig]=N[i];    ( pick up the current value again to use it for the next Iteration of the N array)

} 

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


0

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


0

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.


0

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


1
Dieser Ansatz ist nahezu identisch mit den am häufigsten und am zweithäufigsten bewerteten Antworten auf diese Frage.
Bernhard Barker

0

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.


-1 Quicksort ist O (n log n), genau das hat das OP getan und möchte es verbessern. Sie müssen keine separate Liste verwalten, sondern nur eine Liste mit 100 Nummern. Ihr Vorschlag hat auch den unerwünschten Nebeneffekt, dass Sie die ursprüngliche Liste ändern oder kopieren. Das sind ungefähr 4 GB Speicher, weg.

0
  1. Verwenden Sie das n-te Element, um das 100. Element O (n) zu erhalten.
  2. Iterieren Sie das zweite Mal, aber nur einmal, und geben Sie jedes Element aus, das größer als dieses bestimmte Element ist.

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.


0

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.

public class TopNumber {
    public static void main(String[] args) {
        final int input[] = {2389,8922,3382,6982,5231,8934
                            ,4322,7922,6892,5224,4829,3829
                            ,6892,6872,4682,6723,8923,3492};
        //One int(4 bytes) hold 32 = 2^5 value,
        //About 4 * 125M Bytes
        //int sort[] = new int[1 << (32 - 5)];
        //Allocate small array for local test
        int sort[] = new int[1000];
        //Set all bit to 0
        for(int index = 0; index < sort.length; index++){
            sort[index] = 0;
        }
        for(int number : input){
            sort[number >>> 5] |= (1 << (number % 32));
        }
        int topNum = 0;
        outer:
        for(int index = sort.length - 1; index >= 0; index--){
            if(0 != sort[index]){
                for(int bit = 31; bit >= 0; bit--){
                    if(0 != (sort[index] & (1 << bit))){
                        System.out.println((index << 5) + bit);
                        topNum++;
                        if(topNum >= 3){
                            break outer;
                        }
                    }
                }
            }
        }
    }
}

0

Ich habe meinen eigenen Code gemacht, nicht sicher, ob es das ist, wonach der "Interviewer" aussieht

private static final int MAX=100;
 PriorityQueue<Integer> queue = new PriorityQueue<>(MAX);
        queue.add(array[0]);
        for (int i=1;i<array.length;i++)
        {

            if(queue.peek()<array[i])
            {
                if(queue.size() >=MAX)
                {
                    queue.poll();
                }
                queue.add(array[i]);

            }

        }

0

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:

  • Teilen Sie die Datei in n Teile auf, erstellen Sie n Threads, lassen Sie n Threads jeweils nach den 100 größten Zahlen in ihrem Teil der Datei suchen (mithilfe der Prioritätswarteschlange) und erhalten Sie schließlich die 100 größten Zahlen aller ausgegebenen Threads.
  • Verwenden Sie einen Cluster, um eine solche Aufgabe mit einer Lösung wie hadoop auszuführen. Hier können Sie die Datei noch mehr aufteilen und die Ausgabe für eine Datei mit 1 Milliarde (oder 10 ^ 12) Zahlen schneller ausführen.

0

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.


-1

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.


1
Keine externen Datenstrukturen? Was ist mit dem zu sortierenden Milliarden-Zahlen-Array? Ein Array dieser Größe ist sowohl in Bezug auf die Füllzeit als auch in Bezug auf den Speicherplatz ein enormer Aufwand. Was wäre, wenn alle "großen" Zahlen am falschen Ende des Arrays wären? Sie würden in der Größenordnung von 100 Milliarden Swaps benötigen, um sie in Position zu bringen - ein weiterer großer Overhead ... Schließlich ist M N = 100 Milliarden gegenüber M Log2 (N) = 6,64 Milliarden, was einem Unterschied von fast zwei Größenordnungen entspricht. Vielleicht überdenken Sie diesen. Ein One-Pass-Scan unter Beibehaltung einer Datenstruktur mit den größten Zahlen wird diesen Ansatz erheblich übertreffen.
NealB
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.