Eine interessante Interviewfrage, die ein Kollege von mir verwendet:
Angenommen, Sie erhalten eine sehr lange, unsortierte Liste vorzeichenloser 64-Bit-Ganzzahlen. Wie würden Sie die kleinste nicht negative Ganzzahl finden, die nicht in der Liste vorkommt?
FOLLOW-UP: Nachdem die offensichtliche Lösung durch Sortieren vorgeschlagen wurde, können Sie dies schneller als O (n log n) tun?
FOLLOW-UP: Ihr Algorithmus muss auf einem Computer mit beispielsweise 1 GB Speicher ausgeführt werden
ERKLÄRUNG: Die Liste befindet sich im RAM, obwohl sie möglicherweise eine große Menge davon verbraucht. Sie erhalten die Größe der Liste, z. B. N, im Voraus.
Antworten:
Wenn die Datenstruktur an Ort und Stelle mutiert werden kann und Direktzugriff unterstützt, können Sie dies in O (N) -Zeit und O (1) zusätzlichem Speicherplatz tun. Gehen Sie einfach nacheinander durch das Array und schreiben Sie für jeden Index den Wert am Index in den durch value angegebenen Index, platzieren Sie rekursiv einen Wert an dieser Stelle an seiner Stelle und werfen Sie Werte> N weg. Gehen Sie dann erneut durch das Array und suchen Sie nach dem Punkt Wobei der Wert nicht mit dem Index übereinstimmt - das ist der kleinste Wert, der nicht im Array enthalten ist. Dies führt zu höchstens 3N-Vergleichen und verwendet nur wenige Werte für temporären Speicherplatz.
quelle
Hier ist eine einfache
O(N)
Lösung, dieO(N)
Platz benötigt. Ich gehe davon aus, dass wir die Eingabeliste auf nicht negative Zahlen beschränken und die erste nicht negative Zahl finden möchten, die nicht in der Liste enthalten ist.N
.N
Booleschen Werten zu, die für alle initialisiert sindfalse
.X
in der ListeX
kleiner als istN
, setzen Sie dasX'th
Element des Arrays auftrue
.0
und suchen Sie nach dem ersten Elementfalse
. Wenn Sie den erstenfalse
am Index findenI
,I
ist die Antwort. Andernfalls (dh wenn alle Elemente vorhanden sindtrue
) lautet die AntwortN
.In der Praxis würde das "Array von
N
Booleschen Werten" wahrscheinlich als "Bitmap" oder "Bitset" codiert, das alsbyte
oderint
Array dargestellt wird. Dies benötigt normalerweise weniger Speicherplatz (abhängig von der Programmiersprache) und ermöglichtfalse
eine schnellere Suche nach dem ersten .So / warum funktioniert der Algorithmus.
Angenommen, die
N
Zahlen in der Liste sind nicht unterschiedlich oder eine oder mehrere von ihnen sind größer alsN
. Dies bedeutet, dass mindestens eine Nummer in dem Bereich vorhanden sein muss0 .. N - 1
, der nicht in der Liste enthalten ist. Das Problem, die kleinste fehlende Zahl zu finden, muss sich daher auf das Problem reduzieren, die kleinste fehlende Zahl kleiner als zu findenN
. Dies bedeutet, dass wir keine Zahlen verfolgen müssen, die größer oder gleich sindN
... weil sie nicht die Antwort sind.Die Alternative zum vorherigen Absatz besteht darin, dass die Liste eine Permutation der Zahlen aus ist
0 .. N - 1
. In diesem Fall setzt Schritt 3 alle Elemente des Arrays auftrue
und Schritt 4 sagt uns, dass die erste "fehlende" Nummer istN
.Die rechnerische Komplexität des Algorithmus ist
O(N)
mit einer relativ kleinen Proportionalitätskonstante verbunden. Es werden zwei lineare Durchgänge durch die Liste ausgeführt oder nur ein Durchgang, wenn bekannt ist, dass die Listenlänge damit beginnt. Es ist nicht erforderlich, das Halten der gesamten Liste im Speicher darzustellen, daher ist die asymptotische Speichernutzung des Algorithmus genau das, was zur Darstellung des Arrays von Booleschen Werten erforderlich ist. dhO(N)
Bits.(Im Gegensatz dazu setzen Algorithmen, die auf In-Memory-Sortierung oder -Partitionierung basieren, voraus, dass Sie die gesamte Liste im Speicher darstellen können. In der Form, in der die Frage gestellt wurde, wären dafür
O(N)
64-Bit-Wörter erforderlich .)@Jorn kommentiert, dass die Schritte 1 bis 3 eine Variation der Zählsortierung sind. In gewissem Sinne hat er Recht, aber die Unterschiede sind signifikant:
Xmax - Xmin
Zählern, wobeiXmax
die größte Zahl in der Liste undXmin
die kleinste Zahl in der Liste ist. Jeder Zähler muss in der Lage sein, N Zustände darzustellen; dh unter der Annahme einer binären Darstellung muss sie (mindestens) ganzzahligeceiling(log2(N))
Bits haben.Xmax
und zu bestimmenXmin
.ceiling(log2(N)) * (Xmax - Xmin)
Bits.Im Gegensatz dazu erfordert der oben vorgestellte Algorithmus
N
im schlimmsten und besten Fall einfach Bits.Diese Analyse führt jedoch zu der Intuition, dass der Algorithmus, wenn er die Liste zunächst nach einer Null durchsucht (und bei Bedarf die Listenelemente zählt), eine schnellere Antwort ohne Leerzeichen geben würde, wenn er die Null findet. Es lohnt sich auf jeden Fall, dies zu tun, wenn eine hohe Wahrscheinlichkeit besteht, mindestens eine Null in der Liste zu finden. Und dieser zusätzliche Durchgang ändert nichts an der Gesamtkomplexität.
BEARBEITEN: Ich habe die Beschreibung des Algorithmus geändert, um "Array von Booleschen Werten" zu verwenden, da die Leute meine ursprüngliche Beschreibung mit Bits und Bitmaps anscheinend als verwirrend empfanden.
quelle
bool[]
oder durch eine Bitmap implementieren können, ist für die allgemeine Lösung irrelevant.Da das OP jetzt festgelegt hat, dass die ursprüngliche Liste im RAM gespeichert ist und der Computer nur beispielsweise 1 GB Speicher hat, werde ich mich auf die Probe stellen und vorhersagen, dass die Antwort Null ist.
1 GB RAM bedeutet, dass die Liste höchstens 134.217.728 Nummern enthalten kann. Es gibt jedoch 2 64 = 18.446.744.073.709.551.616 mögliche Zahlen. Die Wahrscheinlichkeit, dass Null in der Liste steht, beträgt 1 zu 137.438.953.472.
Im Gegensatz dazu ist meine Wahrscheinlichkeit, dieses Jahr vom Blitz getroffen zu werden, 1 zu 700.000. Und meine Wahrscheinlichkeit , von einem Meteoriten getroffen zu werden, liegt bei 1 zu 10 Billionen. Daher ist es ungefähr zehnmal wahrscheinlicher, dass ich aufgrund meines vorzeitigen Todes durch ein Himmelsobjekt in eine wissenschaftliche Zeitschrift geschrieben werde, als dass die Antwort nicht Null ist.
quelle
Wie in anderen Antworten erwähnt, können Sie eine Sortierung durchführen und dann einfach nach oben scannen, bis Sie eine Lücke finden.
Sie können die algorithmische Komplexität auf O (N) verbessern und den O (N) -Raum beibehalten, indem Sie einen modifizierten QuickSort verwenden, bei dem Sie Partitionen entfernen, die keine potenziellen Kandidaten für das Eindämmen der Lücke sind.
Dies spart eine große Anzahl von Berechnungen.
quelle
Da die Zahlen alle 64 Bit lang sind, können wir eine Radix-Sortierung verwenden , die O (n) ist. Sortieren Sie sie und scannen Sie sie, bis Sie das finden, wonach Sie suchen.
Wenn die kleinste Zahl Null ist, scannen Sie vorwärts, bis Sie eine Lücke finden. Wenn die kleinste Zahl nicht Null ist, ist die Antwort Null.
quelle
Um eine der Fallstricke des
O(N)
Denkens zu veranschaulichen , ist hier einO(N)
Algorithmus, derO(1)
Raum verwendet.quelle
Für eine platzsparende Methode und alle Werte sind unterschiedlich. Sie können dies räumlich
O( k )
und zeitlich tunO( k*log(N)*N )
. Es ist platzsparend und es werden keine Daten verschoben und alle Operationen sind elementar (Hinzufügen von Subtrahieren).U = N; L=0
k
Regionen. So was:0->(1/k)*(U-L) + L
,0->(2/k)*(U-L) + L
,0->(3/k)*(U-L) + L
...0->(U-L) + L
count{i}
) sich in jeder Region befinden. (N*k
Schritte)h
), die nicht voll ist. Das heißtcount{h} < upper_limit{h}
. (k
Schritte)h - count{h-1} = 1
du deine Antwort hastU = count{h}; L = count{h-1}
Dies kann durch Hashing verbessert werden (danke für Nic diese Idee).
k
Regionen. So was:L + (i/k)->L + (i+1/k)*(U-L)
inc count{j}
mitj = (number - L)/k
(if L < number < U)
h
), die keine k Elemente enthältcount{h} = 1
h deine Antwort istU = maximum value in region h
L = minimum value in region h
Dies wird in laufen
O(log(N)*N)
.quelle
U-L < k
Ich würde sie einfach sortieren und dann die Sequenz durchlaufen, bis ich eine Lücke finde (einschließlich der Lücke am Anfang zwischen Null und der ersten Zahl).
In Bezug auf einen Algorithmus würde so etwas es tun:
Wenn Sie viel mehr Speicher als CPU-Grunzen haben, können Sie natürlich eine Bitmaske aller möglichen 64-Bit-Werte erstellen und einfach die Bits für jede Zahl in der Liste setzen. Suchen Sie dann nach dem ersten 0-Bit in dieser Bitmaske. Das macht es zu einer O (n) -Operation in Bezug auf die Zeit, aber verdammt teuer in Bezug auf den Speicherbedarf :-)
Ich bezweifle, dass Sie O (n) verbessern können, da ich keinen Weg sehe, dies zu tun, bei dem nicht jede Zahl mindestens einmal betrachtet wird.
Der Algorithmus für diesen wäre wie folgt:
quelle
Sortieren Sie die Liste, sehen Sie sich das erste und das zweite Element an und gehen Sie nach oben, bis eine Lücke vorhanden ist.
quelle
Sie können dies in O (n) Zeit und O (1) zusätzlichem Raum tun, obwohl der versteckte Faktor ziemlich groß ist. Dies ist kein praktischer Weg, um das Problem zu lösen, aber es könnte trotzdem interessant sein.
Durchlaufen Sie für jede vorzeichenlose 64-Bit-Ganzzahl (in aufsteigender Reihenfolge) die Liste, bis Sie die Ziel-Ganzzahl finden oder das Ende der Liste erreichen. Wenn Sie das Ende der Liste erreichen, ist die Ziel-Ganzzahl die kleinste Ganzzahl, die nicht in der Liste enthalten ist. Wenn Sie das Ende der 64-Bit-Ganzzahlen erreichen, befindet sich jede 64-Bit-Ganzzahl in der Liste.
Hier ist es als Python-Funktion:
Diese Funktion ist absichtlich ineffizient, um O (n) zu halten. Beachten Sie insbesondere, dass die Funktion die Ziel-Ganzzahlen auch dann überprüft, wenn die Antwort gefunden wurde. Wenn die Funktion zurückgegeben würde, sobald die Antwort gefunden wurde, würde die Häufigkeit, mit der die äußere Schleife ausgeführt wurde, durch die Größe der Antwort gebunden, die durch n gebunden ist. Diese Änderung würde die Laufzeit O (n ^ 2) machen, obwohl sie viel schneller wäre.
quelle
Vielen Dank an egon, swilden und Stephen C für meine Inspiration. Erstens kennen wir die Grenzen des Zielwerts, da dieser nicht größer als die Größe der Liste sein kann. Außerdem könnte eine 1-GB-Liste höchstens 134217728 (128 * 2 ^ 20) 64-Bit-Ganzzahlen enthalten.
Hashing-Teil
Ich schlage vor, Hashing zu verwenden, um unseren Suchraum drastisch zu reduzieren. Zuerst Quadratwurzel die Größe der Liste. Für eine 1-GB-Liste ist das N = 11.586. Richten Sie ein ganzzahliges Array der Größe N ein. Durchlaufen Sie die Liste und nehmen Sie die Quadratwurzel * jeder gefundenen Zahl als Hash. Erhöhen Sie in Ihrer Hash-Tabelle den Zähler für diesen Hash. Als nächstes durchlaufen Sie Ihre Hash-Tabelle. Der erste Bucket, den Sie finden, der nicht der maximalen Größe entspricht, definiert Ihren neuen Suchbereich.
Bitmap-Teil Richten Sie
nun eine reguläre Bitmap ein, die der Größe Ihres neuen Suchraums entspricht, und durchlaufen Sie die Quellliste erneut. Füllen Sie die Bitmap aus, sobald Sie jede Nummer in Ihrem Suchraum finden. Wenn Sie fertig sind, gibt Ihnen das erste nicht gesetzte Bit in Ihrer Bitmap Ihre Antwort.
Dies wird in O (n) Zeit und O (sqrt (n)) Raum abgeschlossen.
(* Sie könnten so etwas wie Bitverschiebung verwenden, um dies viel effizienter zu tun, und einfach die Anzahl und Größe der Eimer entsprechend variieren.)
quelle
Wenn in einer Liste von Zahlen nur eine Zahl fehlt, können Sie die fehlende Zahl am einfachsten ermitteln, indem Sie die Reihen summieren und jeden Wert in der Liste subtrahieren. Der Endwert ist die fehlende Zahl.
quelle
quelle
Wir könnten eine Hash-Tabelle verwenden, um die Zahlen zu speichern. Sobald alle Zahlen fertig sind, führen Sie einen Zähler von 0 aus, bis wir den niedrigsten finden. Ein einigermaßen guter Hash wird in konstanter Zeit gehasht und gespeichert und in konstanter Zeit abgerufen.
Der schlimmste Fall, wenn sich
n
Elemente im Array befinden{0, 1, ... n-1}
und in diesem Fall die Antwort erhalten wirdn
, wobei diese weiterhin beibehalten wirdO(n)
.quelle
Hier ist meine Antwort in Java geschrieben:
Grundidee: 1- Durchlaufen Sie das Array und werfen Sie doppelte positive, Nullen und negative Zahlen weg, während Sie den Rest zusammenfassen, die maximale positive Zahl erhalten und die eindeutigen positiven Zahlen in einer Karte behalten.
2- Berechnen Sie die Summe als max * (max + 1) / 2.
3- Ermitteln Sie die Differenz zwischen den in den Schritten 1 und 2 berechneten Beträgen
4- Wiederholen Sie die Schleife von 1 bis zum Minimum von [Summen Differenz, max] und geben Sie die erste Zahl zurück, die nicht in der in Schritt 1 aufgefüllten Karte enthalten ist.
quelle
Wie Stephen C klug hervorhob, muss die Antwort eine Zahl sein, die kleiner als die Länge des Arrays ist. Ich würde dann die Antwort durch binäre Suche finden. Dies optimiert den schlimmsten Fall (sodass der Interviewer Sie in einem pathologischen "Was wäre wenn" -Szenario nicht fangen kann). Weisen Sie in einem Interview darauf hin, dass Sie dies tun, um für den schlimmsten Fall zu optimieren.
Die Verwendung der binären Suche besteht darin, die gesuchte Zahl von jedem Element des Arrays zu subtrahieren und nach negativen Ergebnissen zu suchen.
quelle
Ich mag die "Rate Null" Apprach. Wenn die Zahlen zufällig waren, ist Null sehr wahrscheinlich. Wenn der "Prüfer" eine nicht zufällige Liste erstellt hat, fügen Sie eine hinzu und raten Sie erneut:
Der schlimmste Fall ist n * N mit n = N, aber in der Praxis ist n höchstwahrscheinlich eine kleine Zahl (z. B. 1).
quelle
Ich bin mir nicht sicher, ob ich die Frage habe. Wenn jedoch für Liste 1,2,3,5,6 die fehlende Zahl 4 ist, kann die fehlende Zahl in O (n) gefunden werden durch: (n + 2) (n + 1) / 2- (n + 1) n / 2
EDIT: Entschuldigung, ich denke ich habe letzte Nacht zu schnell nachgedacht. Wie auch immer, der zweite Teil sollte eigentlich durch Summe (Liste) ersetzt werden, woher O (n) kommt. Die Formel enthüllt die Idee dahinter: Für n aufeinanderfolgende ganze Zahlen sollte die Summe (n + 1) * n / 2 sein. Wenn eine Zahl fehlt, entspricht die Summe der Summe der (n + 1) aufeinanderfolgenden ganzen Zahlen abzüglich der fehlenden Zahl.
Vielen Dank, dass Sie darauf hingewiesen haben, dass ich einige Mittelstücke in meinem Kopf hatte.
quelle
Gut gemacht Ants Aasma! Ich dachte ungefähr 15 Minuten lang über die Antwort nach und fand unabhängig eine Antwort in einer ähnlichen Denkweise wie Ihre:
m steht für "die aktuell maximal mögliche Ausgabe, wenn ich weiß, was ich über die ersten i-Eingaben weiß und bis zum Eintrag bei m-1 nichts anderes über die Werte annehme".
Dieser Wert von m wird nur zurückgegeben, wenn (a [i], ..., a [m-1]) eine Permutation der Werte (i, ..., m-1) ist. Wenn also a [i]> = m oder wenn a [i] <i oder wenn a [i] == a [a [i]] ist, wissen wir, dass m die falsche Ausgabe ist und mindestens ein Element niedriger sein muss. Wenn wir also m dekrementieren und a [i] gegen a [m] tauschen, können wir rekursieren.
Wenn dies nicht wahr ist, sondern ein [i]> i, dann wissen wir, dass a [i]! = A [a [i]], dass das Austauschen eines [i] gegen ein [a [i]] die Anzahl der Elemente erhöht an ihrem eigenen Platz.
Andernfalls muss a [i] gleich i sein. In diesem Fall können wir i inkrementieren, da wir wissen, dass alle Werte von bis einschließlich dieses Index gleich ihrem Index sind.
Der Beweis, dass dies nicht in eine Endlosschleife eintreten kann, bleibt dem Leser als Übung überlassen. :) :)
quelle
Das Dafny- Fragment aus Ants 'Antwort zeigt, warum der In-Place-Algorithmus möglicherweise fehlschlägt. Die
requires
Vorbedingung beschreibt, dass die Werte jedes Elements nicht über die Grenzen des Arrays hinausgehen dürfen.Fügen Sie den Code mit und ohne
forall ...
Klausel in den Validator ein , um den Überprüfungsfehler anzuzeigen. Der zweite Fehler ist darauf zurückzuführen, dass der Prüfer keine Beendigungsbedingung für die Pass 1-Schleife festlegen kann. Dies zu beweisen, bleibt jemandem überlassen, der das Tool besser versteht.quelle
Hier ist eine Antwort in Java, die die Eingabe nicht ändert und O (N) -Zeit und N Bits sowie einen kleinen konstanten Speicheraufwand verwendet (wobei N die Größe der Liste ist):
quelle
Erhielt 100% für die obige Lösung.
quelle
1) Negativ und Null filtern
2) Sortieren / unterscheiden
3) Array besuchen
Komplexität : O (N) oder O (N * log (N))
mit Java8
quelle
Ein ungeordnetes_Set kann verwendet werden, um alle positiven Zahlen zu speichern. Anschließend können wir von 1 bis zur Länge des ungeordneten_Sets iterieren und die erste Zahl sehen, die nicht vorkommt.
quelle
Lösung durch einfaches Javascript
Hoffe das hilft jemandem.
quelle
Mit Python ist es nicht das effizienteste, aber richtig
quelle
quelle
das kann helfen:
quelle