Finden Sie die kleinste Ganzzahl, die nicht in einer Liste enthalten ist

86

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.

PeterAllenWebb
quelle
6
Ich denke, Sie können den nicht negativen Teil weglassen, wenn Sie sehen, wie Sie von einer vorzeichenlosen Ganzzahl sprechen.
KevenDenen
4
Die Frage ist ziemlich einfach, es sei denn, ich bin außerhalb der Basis, IMO, aber wie andere erwähnt haben, gibt es Fragen zu stellen oder Annahmen, die angegeben werden sollten.
James Black
8
@paxdiablo: Dies ist ein Fall, in dem das Sagen von O (n) nicht so viel bedeutet. Selbst wenn Sie Ihr 2 ^ 64-Bit-Array auf Tontafeln auf der Osterinsel speichern und mit einer Brieftaube darauf zugreifen, ist der Algorithmus immer noch O (n).
IJ Kennedy
6
Das Ändern der Speicheranforderungen zur Hälfte macht dies zu einer großartigen Interviewfrage ;-)
Chris Ballance
1
Ich finde es amüsant, dass alle Antworten dieselbe allgemeine Lösung haben (sortieren Sie das Array und finden Sie den ersten Wert, der die Sequenz unterbricht), aber alle verwenden eine andere Sortierung. (Geänderte Quicksortierung, Radix-Sortierung, ...) Die akzeptierte Antwort entspricht einer Zählsortierung, bei der Elemente über N
verworfen werden

Antworten:

119

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.

# Pass 1, move every value to the position of its value
for cursor in range(N):
    target = array[cursor]
    while target < N and target != array[target]:
        new_target = array[target]
        array[target] = target
        target = new_target

# Pass 2, find first location where the index doesn't match the value
for cursor in range(N):
    if array[cursor] != cursor:
        return cursor
return N
Ameisen Aasma
quelle
9
Kleiner Trottel. Sie haben einen trivialen Fall verpasst: Wenn die Liste {0, ..., N-1} ist. In diesem Fall bewirkt Pass 1 nichts und in Pass 2 Array [Cursor] == Cursor für alle Einträge in der Liste, sodass der Algorithmus nicht zurückgibt. Sie benötigen also am Ende eine 'return N'-Anweisung.
Alex
12
Ihre Lösung verbindet die Domäne und den Bereich (Ziel ist sowohl ein Wert als auch ein Index). Der Bereich ist durch den verfügbaren Speicher auf 128 Millionen Elemente begrenzt, die Domäne ist jedoch 2 GB groß. Es schlägt mit einem einzelnen Eintrag fehl, dessen Wert größer ist als die Anzahl der Einträge, die dem Array zugewiesen werden können. Wenn in der Frage nicht "sehr lang" angegeben wurde, ist die Antwort elegant, auch wenn die Eingabe dadurch zerstört wird. Der Zeit-Raum-Kompromiss ist bei diesem Problem sehr offensichtlich, und eine O (N) -Lösung ist unter den bereitgestellten Einschränkungen möglicherweise nicht möglich.
Pekka
2
Der zweite Durchgang könnte eine binäre Suche anstelle einer linearen Suche verwenden.
user448810
4
Diese Lösung funktioniert nur, wenn der Wertebereich und der Index vergleichbar sind.
Dubby
7
Es funktioniert gut mit größeren Werten. Die größeren Werte können ignoriert werden, da sie nichts mit dem kleinsten Wert zu tun haben, der nicht im Array enthalten ist. In Ihrem Beispiel durchläuft der erste Durchgang das Array und ignoriert alle Werte aufgrund des Ziels <N. Bei der ersten Iteration des zweiten Durchgangs wird dann 0 zurückgegeben.
Ameisen Aasma
89

Hier ist eine einfache O(N)Lösung, die O(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.

  1. Finden Sie die Länge der Liste; Sagen wir es ist N.
  2. Ordnen Sie ein Array von NBooleschen Werten zu, die für alle initialisiert sind false.
  3. Wenn für jede Zahl Xin der Liste Xkleiner als ist N, setzen Sie das X'thElement des Arrays auf true.
  4. Scannen Sie das Array ausgehend vom Index 0und suchen Sie nach dem ersten Element false. Wenn Sie den ersten falseam Index finden I, Iist die Antwort. Andernfalls (dh wenn alle Elemente vorhanden sind true) lautet die Antwort N.

In der Praxis würde das "Array von NBooleschen Werten" wahrscheinlich als "Bitmap" oder "Bitset" codiert, das als byteoder intArray dargestellt wird. Dies benötigt normalerweise weniger Speicherplatz (abhängig von der Programmiersprache) und ermöglicht falseeine schnellere Suche nach dem ersten .


So / warum funktioniert der Algorithmus.

Angenommen, die NZahlen in der Liste sind nicht unterschiedlich oder eine oder mehrere von ihnen sind größer als N. Dies bedeutet, dass mindestens eine Nummer in dem Bereich vorhanden sein muss 0 .. 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 sind N... 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 auf trueund Schritt 4 sagt uns, dass die erste "fehlende" Nummer ist N.


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. dh O(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:

  • Eine Zählsortierung erfordert ein Array von (mindestens) Xmax - XminZählern, wobei Xmaxdie größte Zahl in der Liste und Xmindie 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) ganzzahlige ceiling(log2(N))Bits haben.
  • Um die Arraygröße zu bestimmen, muss eine Zählsortierung einen ersten Durchgang durch die Liste machen, um Xmaxund zu bestimmen Xmin.
  • Der minimale Platzbedarf im ungünstigsten Fall beträgt daher ceiling(log2(N)) * (Xmax - Xmin)Bits.

Im Gegensatz dazu erfordert der oben vorgestellte Algorithmus Nim 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.

Stephen C.
quelle
3
@ adi92 Wenn Sie in Schritt 3 eine Bitmap mit allen auf 1 gesetzten Bits erhalten, enthält die Liste jeden Wert von 0 bis N-1. Das bedeutet, dass die kleinste nicht negative Ganzzahl in der Liste N ist. Wenn zwischen 0 und N-1 ein Wert vorhanden ist, der NICHT in der Liste enthalten ist, wird das entsprechende Bit nicht gesetzt. Der kleinste solche Wert ist daher die Antwort.
Divegeek
4
@ adi92 In Ihrem Beispiel würde die Liste 300 Elemente enthalten. Das heißt, wenn ein "fehlender" Wert vorhanden ist, muss er kleiner als 300 sein. Wenn wir den Algorithmus ausführen, erstellen wir ein Bitfeld mit 300 Slots und setzen dann wiederholt die Bits in den Slots 1, 2 und 3, wobei alle übrig bleiben Die anderen Slots - 0 und 4 bis 299 - sind frei. Beim Scannen des Bitfelds wird das Flag in Steckplatz 0 gelöscht, sodass wir wissen, dass 0 die Antwort ist.
Divegeek
4
Beachten Sie, dass dieser Algorithmus möglicherweise einfacher zu verstehen ist, ohne dass das Bit herumwirbelt: "Erstellen Sie ein Boolesches Array der Größe N" usw. Wenn Sie es auf diese Weise verstanden haben, ist der Übergang zu einer bitweisen Version konzeptionell einfach.
Jon Skeet
2
Verwenden Sie bei der Abgabe einer abstrakten Lösung die konzeptionell einfachste Methode, die funktioniert, und spezialisieren Sie sich nicht übermäßig. Ihre Lösung schreit nach der Verwendung eines (abstrakten) booleschen Arrays. Nennen Sie es also so. Dass Sie dieses Array durch bool[]oder durch eine Bitmap implementieren können, ist für die allgemeine Lösung irrelevant.
Joren
2
Ich denke, diese Lösung lässt sich am besten beschreiben durch "Verwenden Sie eine Zählsortierung, bei der Elemente über N nicht berücksichtigt werden, und suchen Sie dann das erste fehlende Element, indem Sie von Anfang an eine lineare Suche durchführen."
Joren
13

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.

Barry Brown
quelle
11
Ihre Berechnung gilt nur, wenn die Werte gleichmäßig verteilt und zufällig ausgewählt sind. Sie hätten genauso gut nacheinander erzeugt werden können.
Divegeek
1
Du hast natürlich recht. Aber ich bin ganz auf die Optimierung für den allgemeinen Fall ausgerichtet. :)
Barry Brown
10
Wie hoch ist die Wahrscheinlichkeit, dass der Befragte mit dieser Antwort ausgewählt wird?
Amarghosh
6
Die Frage besagt nicht, dass die Zahlen einheitlich zufällig ausgewählt werden. Sie werden von der Person ausgewählt, die diese Frage stellt. Angesichts dessen ist die Wahrscheinlichkeit, dass 0 in der Liste steht, viel größer als 1 in 137.438.953.472, wahrscheinlich sogar größer als 1 in 2 .:-)
ShreevatsaR
8
@Amarghosh Die Antwort auf diese Frage ist ebenfalls Null.
PeterAllenWebb
10

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.

  • Entfernen Sie in der ersten Partitionsphase Duplikate.
  • Überprüfen Sie nach Abschluss der Partitionierung die Anzahl der Elemente in der unteren Partition
  • Entspricht dieser Wert dem Wert, der zum Erstellen der Partition verwendet wird?
    • Wenn ja, bedeutet dies, dass sich die Lücke in der höheren Partition befindet.
      • Fahren Sie mit dem Quicksort fort und ignorieren Sie die untere Partition
    • Ansonsten befindet sich die Lücke in der unteren Partition
      • Fahren Sie mit dem Quicksort fort und ignorieren Sie die höhere Partition

Dies spart eine große Anzahl von Berechnungen.

cdiggins
quelle
Das ist ziemlich geschickt. Es wird davon ausgegangen, dass Sie die Länge der Partition in weniger als linearer Zeit berechnen können. Dies ist möglich, wenn diese zusammen mit dem Partitionsarray gespeichert wird. Es wird auch davon ausgegangen, dass die ursprüngliche Liste im RAM gespeichert ist.
Barry Brown
2
Wenn Sie die Länge der Liste kennen, können Sie auch Werte auswählen, die größer als len (Liste) sind. Nach dem Pigeonhole-Prinzip müssen alle "Löcher" kleiner als len (Liste) sein.
Divegeek
1
Ich glaube nicht, dass das O (n) ist ... Zum einen bin ich mir nicht sicher, ob Sie Duplikate entfernen können, bis eine Liste vollständig sortiert ist. Zweitens können Sie zwar garantieren, dass bei jeder Iteration die Hälfte des Suchraums weggeworfen wird (weil Sie in unter und über dem Mittelpunkt unterteilt haben), aber Sie haben immer noch mehrere Durchgänge (abhängig von n) über Daten, die von n abhängig sind.
Paxdiablo
1
paxdiablo: Sie können eine neue Liste mit nur eindeutigen Werten erstellen, indem Sie eine Bitmap-Methode verwenden, wie sie Stephen C vorgeschlagen hat. Dies läuft in O (n) Zeit und Raum. Ich bin mir nicht sicher, ob es besser geht.
Nic
8

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.

Barry Brown
quelle
Stimmt, aber die Speicheranforderungen könnten für die Radix-Sortierung ziemlich hoch werden.
PeterAllenWebb
1
Die Radix-Sortierung funktioniert nicht für sehr große Datenmengen. Aber Partition und Radix-Sortierung könnten funktionieren.
DarthVader
8

Um eine der Fallstricke des O(N)Denkens zu veranschaulichen , ist hier ein O(N)Algorithmus, der O(1)Raum verwendet.

for i in [0..2^64):
  if i not in list: return i

print "no 64-bit integers are missing"
IJ Kennedy
quelle
1
Will hat recht. Dies ist nicht O (n), da Sie hier tatsächlich zwei Schleifen haben, aber eine ist implizit. Das Bestimmen, ob sich ein Wert in einer Liste befindet, ist eine O (n) -Operation, und das tun Sie n-mal in Ihrer for-Schleife. Das macht es zu O (n ^ 2).
Nic
6
Nic, Will, es ist O (n * N), wobei n die Größe der Liste und N die Größe der Domäne ist (64-Bit-Ganzzahlen). Während N eine große Zahl ist, ist es immer noch eine Konstante, so formal ist die Komplexität für das angegebene Problem O (n).
Ants Aasma
1
Ameisen, ich stimme zu, dass es O (n N) ist, aber N ist nicht konstant. Da der Algorithmus beendet ist, wenn er die Antwort gefunden hat, entspricht die Anzahl der vollständigen Iterationen durch die äußere Schleife der Antwort, die selbst an die Größe der Liste gebunden ist. In diesem Fall ist O (N n) also O (n ^ 2).
Will Harris
12
Das Suchen nach einer Zahl in einer Liste von N Elementen ist eindeutig O (N). Wir machen das 2 ^ 64 mal. Während groß, ist 2 ^ 64 eine Konstante. Daher ist der Algorithmus C * O (N), was immer noch O (N) ist.
IJ Kennedy
3
Ich muss meine vorherige Aussage widerrufen; Nach der strengsten Definition ist diese Operation tatsächlich O (n).
Nic
5

Für eine platzsparende Methode und alle Werte sind unterschiedlich. Sie können dies räumlich O( k )und zeitlich tun O( k*log(N)*N ). Es ist platzsparend und es werden keine Daten verschoben und alle Operationen sind elementar (Hinzufügen von Subtrahieren).

  1. einstellen U = N; L=0
  2. Partitionieren Sie zuerst den Nummernraum in kRegionen. So was:
    • 0->(1/k)*(U-L) + L, 0->(2/k)*(U-L) + L, 0->(3/k)*(U-L) + L...0->(U-L) + L
  3. Finden Sie heraus, wie viele Zahlen ( count{i}) sich in jeder Region befinden. ( N*kSchritte)
  4. Suchen Sie die erste Region ( h), die nicht voll ist. Das heißt count{h} < upper_limit{h}. ( kSchritte)
  5. wenn h - count{h-1} = 1du deine Antwort hast
  6. einstellen U = count{h}; L = count{h-1}
  7. gehe zu 2

Dies kann durch Hashing verbessert werden (danke für Nic diese Idee).

  1. gleich
  2. Partitionieren Sie zuerst den Nummernraum in kRegionen. So was:
    • L + (i/k)->L + (i+1/k)*(U-L)
  3. inc count{j} mit j = (number - L)/k (if L < number < U)
  4. Finde die erste Region ( h), die keine k Elemente enthält
  5. wenn count{h} = 1h deine Antwort ist
  6. einstellen U = maximum value in region h L = minimum value in region h

Dies wird in laufen O(log(N)*N).

Egon
quelle
Diese Antwort gefällt mir sehr gut. Es war ein bisschen schwer zu lesen, aber es ist sehr ähnlich zu dem, was ich in meinem Kopf hatte, als ich die Frage las.
Nic
auch irgendwann wäre es klug, auf diese Bitmap-Lösung von Stephen C. U-L < k
Egon
Dies läuft nicht in O (log (N) * N), sondern in O (N). Ihre Antwort ist eine Verallgemeinerung der @ cdiggins-Antwort und läuft in O (N), weil Summe (1 / k ** i für i im Bereich (Ceil (log_k (n)))) <= 2.
Lapinot
Bei jeder Iteration, die Sie durch O (N) -Nummern gehen, werden O (log_k (N)) Gesamtiterationen benötigt. Daher ist O (log_k (N) * N) == O (log (N) * N). Die ursprünglichen Nummern sind nicht sortiert / mit einem Bucket versehen, und Sie müssen alle durchgehen.
Egon
Wenn Sie die ursprüngliche Liste jedoch in k Regionen (mit der Größe n / k) partitioniert haben, wählen Sie die erste Region aus, die nicht voll ist. Daher müssen Sie in der nächsten Iteration nur die ausgewählte Region berücksichtigen und in k neue Regionen (mit der Größe n / k ** 2) usw. unterteilen. Tatsächlich iterieren Sie nicht jedes Mal auf der gesamten Liste (sonst ist der Punkt der Partitionierung ?).
Lapinot
3

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:

def smallest_not_in_list(list):
    sort(list)
    if list[0] != 0:
        return 0
    for i = 1 to list.last:
        if list[i] != list[i-1] + 1:
            return list[i-1] + 1
    if list[list.last] == 2^64 - 1:
        assert ("No gaps")
    return list[list.last] + 1

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:

def smallest_not_in_list(list):
    bitmask = mask_make(2^64) // might take a while :-)
    mask_clear_all (bitmask)
    for i = 1 to list.last:
        mask_set (bitmask, list[i])
    for i = 0 to 2^64 - 1:
        if mask_is_clear (bitmask, i):
            return i
    assert ("No gaps")
paxdiablo
quelle
Aus der Beschreibung geht hervor, dass 0 bis zum ersten Element ausgeschlossen sind, da es das kleinste ist, das nicht in der Liste enthalten ist. Aber das ist eine Annahme, die ich gemacht habe, ich könnte mich irren.
James Black
Meine Gedanken waren, dass wenn die sortierte Sequenz 4,5,6 wäre, 0 die kleinste wäre, die nicht in der Liste enthalten ist.
Paxdiablo
Ich erwarte, dass 2, 3, 5, die Antwort 4 sein sollte, aber ich könnte mich irren.
James Black
Eine Frage, die vom OP beantwortet werden sollte. Ist der Suchraum "alle 64-Bit-Ganzzahlen ohne Vorzeichen" oder "alle Zahlen zwischen der niedrigsten und der höchsten in der Liste"?
Paxdiablo
Ich bin damit einverstanden, dass Sie im schlimmsten Fall mindestens einmal suchen müssen, es sei denn, es wurde möglicherweise bereits in einem Binärbaum sortiert.
James Black
2

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.

James Black
quelle
Hängt davon ab, wie Sie definieren, Nicht in der Liste.
James Black
@PeterAllenWebb - Es wird geben, aber sind die Zahlen in zufälliger Reihenfolge oder sortiert?
James Black
1

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:

def smallest_missing_uint64(source_list):
    the_answer = None

    target = 0L
    while target < 2L**64:

        target_found = False
        for item in source_list:
            if item == target:
                target_found = True

        if not target_found and the_answer is None:
            the_answer = target

        target += 1L

    return the_answer

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.

Will Harris
quelle
Wahr. Es ist amüsant, wie schrecklich einige der Algorithmen, die O (1) Raum und O (n) Zeit sind, in der Praxis mit dieser Frage versagen.
PeterAllenWebb
1

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

Nic
quelle
1
Ich mag die Idee, den Suchraum in Root-N-Buckets zu unterteilen, um den Speicherbedarf zu verringern, aber Duplikate in der Liste würden diese Methode beschädigen. Ich frage mich, ob es behoben werden kann.
PeterAllenWebb
Sie haben Recht, ich habe es versäumt, doppelte Einträge zu berücksichtigen. Ich bin mir nicht sicher, ob das umgangen werden kann.
Nic
1

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.

Jeff Lundstrom
quelle
Ja. Das ist eine weitere klassische Interviewfrage.
PeterAllenWebb
1
Noch einfacher ist es, die Zahlen in der Liste zusammen zu XOR, die Zahlen im Bereich zusammen XOR und die Ergebnisse zusammen XOR.
John Kurlak
1
 int i = 0;
            while ( i < Array.Length)
            {

                if (Array[i] == i + 1)
                {
                    i++;
                }

                if (i < Array.Length)
                {
                    if (Array[i] <= Array.Length)
                    {//SWap

                        int temp = Array[i];
                        int AnoTemp = Array[temp - 1];
                        Array[temp - 1] = temp;
                        Array[i] = AnoTemp;

                    }
                    else
                       i++;



                }
            }

            for (int j = 0; j < Array.Length; j++)
            {
                if (Array[j] > Array.Length)
                {
                    Console.WriteLine(j + 1);
                    j = Array.Length;
                }
                else
                    if (j == Array.Length - 1)
                        Console.WriteLine("Not Found !!");

            }
        }
rana_stack
quelle
1

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.

for every i in X         // One scan Θ(1)
   hashtable.put(i, i);  // O(1)

low = 0;

while (hashtable.get(i) <> null)   // at most n+1 times
   low++;

print low;

Der schlimmste Fall, wenn sich nElemente im Array befinden {0, 1, ... n-1}und in diesem Fall die Antwort erhalten wird n, wobei diese weiterhin beibehalten wird O(n).

Milind C.
quelle
1

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.

public static int solution(int[] A) {
    if (A == null || A.length == 0) {
        throw new IllegalArgumentException();
    }

    int sum = 0;
    Map<Integer, Boolean> uniqueNumbers = new HashMap<Integer, Boolean>();
    int max = A[0];
    for (int i = 0; i < A.length; i++) {
        if(A[i] < 0) {
            continue;
        }
        if(uniqueNumbers.get(A[i]) != null) {
            continue;
        }
        if (A[i] > max) {
            max = A[i];
        }
        uniqueNumbers.put(A[i], true);
        sum += A[i];
    }
    int completeSum = (max * (max + 1)) /  2;
    for(int j = 1; j <= Math.min((completeSum - sum), max); j++) {
        if(uniqueNumbers.get(j) == null) { //O(1)
            return j;
        }
    }
    //All negative case
    if(uniqueNumbers.isEmpty()) {
        return 1;
    }
    return 0;
}
Rami
quelle
0

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.

Emilio M Bumachar
quelle
0

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:

LowNum=0
i=0
do forever {
  if i == N then leave /* Processed entire array */
  if array[i] == LowNum {
     LowNum++
     i=0
     }
   else {
     i++
   }
}
display LowNum

Der schlimmste Fall ist n * N mit n = N, aber in der Praxis ist n höchstwahrscheinlich eine kleine Zahl (z. B. 1).

NealB
quelle
0

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.

Codismus
quelle
1
Ich sehe auf den ersten Blick nicht, wie das funktionieren würde. In Ihrem Fall ist n = 5 und die Formeln werden festgelegt, unabhängig davon, welche Nummer darin fehlte.
Schwester
Simon: Könnten Sie jetzt bitte die Abstimmung gemäß meiner Bearbeitung entfernen?
Codism
0

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:

#define SWAP(x,y) { numerictype_t tmp = x; x = y; y = tmp; }
int minNonNegativeNotInArr (numerictype_t * a, size_t n) {
    int m = n;
    for (int i = 0; i < m;) {
        if (a[i] >= m || a[i] < i || a[i] == a[a[i]]) {
            m--;
            SWAP (a[i], a[m]);
            continue;
        }
        if (a[i] > i) {
            SWAP (a[i], a[a[i]]);
            continue;
        }
        i++;
    }
    return m;
}

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

Paul Hsieh
quelle
0

Das Dafny- Fragment aus Ants 'Antwort zeigt, warum der In-Place-Algorithmus möglicherweise fehlschlägt. Die requiresVorbedingung beschreibt, dass die Werte jedes Elements nicht über die Grenzen des Arrays hinausgehen dürfen.

method AntsAasma(A: array<int>) returns (M: int)
  requires A != null && forall N :: 0 <= N < A.Length ==> 0 <= A[N] < A.Length;
  modifies A; 
{
  // Pass 1, move every value to the position of its value
  var N := A.Length;
  var cursor := 0;
  while (cursor < N)
  {
    var target := A[cursor];
    while (0 <= target < N && target != A[target])
    {
        var new_target := A[target];
        A[target] := target;
        target := new_target;
    }
    cursor := cursor + 1;
  }

  // Pass 2, find first location where the index doesn't match the value
  cursor := 0;
  while (cursor < N)
  {
    if (A[cursor] != cursor)
    {
      return cursor;
    }
    cursor := cursor + 1;
  }
  return N;
}

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.

Pekka
quelle
0

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

int smallestMissingValue(List<Integer> values) {
    BitSet bitset = new BitSet(values.size() + 1);
    for (int i : values) {
        if (i >= 0 && i <= values.size()) {
            bitset.set(i);
        }
    }
    return bitset.nextClearBit(0);
}
Dave L.
quelle
0
def solution(A):

index = 0
target = []
A = [x for x in A if x >=0]

if len(A) ==0:
    return 1

maxi = max(A)
if maxi <= len(A):
    maxi = len(A)

target = ['X' for x in range(maxi+1)]
for number in A:
    target[number]= number

count = 1
while count < maxi+1:
    if target[count] == 'X':
        return count
    count +=1
return target[count-1] + 1

Erhielt 100% für die obige Lösung.

Angelo
quelle
0

1) Negativ und Null filtern

2) Sortieren / unterscheiden

3) Array besuchen

Komplexität : O (N) oder O (N * log (N))

mit Java8

public int solution(int[] A) {
            int result = 1;
    boolean found = false;
    A = Arrays.stream(A).filter(x -> x > 0).sorted().distinct().toArray();
    //System.out.println(Arrays.toString(A));
    for (int i = 0; i < A.length; i++) {
        result = i + 1;
        if (result != A[i]) {
            found = true;
            break;
        }
    }
    if (!found && result == A.length) {
        //result is larger than max element in array
        result++;
    }
    return result;
}
Abdullah Lubbadeh
quelle
0

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.

int firstMissingPositive(vector<int>& nums) {

    unordered_set<int> fre;
    // storing each positive number in a hash.
    for(int i = 0; i < nums.size(); i +=1)
    {
        if(nums[i] > 0)
            fre.insert(nums[i]);
     }

    int i = 1;
    // Iterating from 1 to size of the set and checking 
    // for the occurrence of 'i'

    for(auto it = fre.begin(); it != fre.end(); ++it)
    {
        if(fre.find(i) == fre.end())
            return i;
        i +=1;
    }

    return i;
}
Mohit Anand
quelle
0

Lösung durch einfaches Javascript

var a = [1, 3, 6, 4, 1, 2];

function findSmallest(a) {
var m = 0;
  for(i=1;i<=a.length;i++) {
    j=0;m=1;
    while(j < a.length) {
      if(i === a[j]) {
        m++;
      }
      j++;
    }
    if(m === 1) {
      return i;
    }
  }
}

console.log(findSmallest(a))

Hoffe das hilft jemandem.

Mano
quelle
0

Mit Python ist es nicht das effizienteste, aber richtig

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
import datetime

# write your code in Python 3.6

def solution(A):
    MIN = 0
    MAX = 1000000
    possible_results = range(MIN, MAX)

    for i in possible_results:
        next_value = (i + 1)
        if next_value not in A:
            return next_value
    return 1

test_case_0 = [2, 2, 2]
test_case_1 = [1, 3, 44, 55, 6, 0, 3, 8]
test_case_2 = [-1, -22]
test_case_3 = [x for x in range(-10000, 10000)]
test_case_4 = [x for x in range(0, 100)] + [x for x in range(102, 200)]
test_case_5 = [4, 5, 6]
print("---")
a = datetime.datetime.now()
print(solution(test_case_0))
print(solution(test_case_1))
print(solution(test_case_2))
print(solution(test_case_3))
print(solution(test_case_4))
print(solution(test_case_5))
Smentek
quelle
0
def solution(A):
    A.sort()
    j = 1
    for i, elem in enumerate(A):
        if j < elem:
            break
        elif j == elem:
            j += 1
            continue
        else:
            continue
    return j
orfeu
quelle
0

das kann helfen:

0- A is [5, 3, 2, 7];
1- Define B With Length = A.Length;                            (O(1))
2- initialize B Cells With 1;                                  (O(n))
3- For Each Item In A:
        if (B.Length <= item) then B[Item] = -1                (O(n))
4- The answer is smallest index in B such that B[index] != -1  (O(n))
Hamed
quelle
Unterscheidet sich dies von Stephen Cs Antwort ? Wie?
Graubart