Ich hatte vor einiger Zeit ein interessantes Vorstellungsgespräch. Die Frage begann ganz einfach:
Q1 : Wir haben eine Tasche mit Zahlen
1
,2
,3
, ...,100
. Jede Zahl erscheint genau einmal, es gibt also 100 Zahlen. Jetzt wird eine Nummer zufällig aus der Tasche gezogen. Finde die fehlende Zahl.
Ich habe diese Interviewfrage natürlich schon einmal gehört, also habe ich sehr schnell geantwortet:
A1 : Nun, die Summe der Zahlen
1 + 2 + 3 + … + N
ist(N+1)(N/2)
(siehe Wikipedia: Summe der arithmetischen Reihen ). DennN = 100
die Summe ist5050
.Wenn also alle Zahlen in der Tasche vorhanden sind, ist die Summe genau
5050
. Da eine Zahl fehlt, ist die Summe geringer, und der Unterschied ist diese Zahl. So können wir diese fehlende Zahl inO(N)
Zeit undO(1)
Raum finden.
Zu diesem Zeitpunkt dachte ich, ich hätte es gut gemacht, aber plötzlich nahm die Frage eine unerwartete Wendung:
F2 : Das ist richtig, aber wie würden Sie das tun, wenn ZWEI Zahlen fehlen?
Ich hatte diese Variante noch nie gesehen / gehört / in Betracht gezogen, geriet in Panik und konnte die Frage nicht beantworten. Der Interviewer bestand darauf, meinen Denkprozess zu kennen, und ich erwähnte, dass wir vielleicht mehr Informationen erhalten können, indem wir ihn mit dem erwarteten Produkt vergleichen oder vielleicht einen zweiten Durchgang machen, nachdem wir einige Informationen aus dem ersten Durchgang usw. gesammelt haben, aber ich habe wirklich nur geschossen im Dunkeln, anstatt tatsächlich einen klaren Weg zur Lösung zu haben.
Der Interviewer hat versucht, mich zu ermutigen, indem er sagte, dass eine zweite Gleichung tatsächlich eine Möglichkeit ist, das Problem zu lösen. Zu diesem Zeitpunkt war ich etwas verärgert (weil ich die Antwort nicht vorher kannte) und fragte, ob dies eine allgemeine (sprich: "nützliche") Programmiertechnik ist oder ob es nur eine Trick / Gotcha-Antwort ist.
Die Antwort des Interviewers überraschte mich: Sie können die Technik verallgemeinern, um 3 fehlende Zahlen zu finden. Tatsächlich können Sie es verallgemeinern, um k fehlende Zahlen zu finden .
Qk : Wenn genau k Zahlen in der Tasche fehlen, wie würden Sie sie effizient finden?
Dies war vor ein paar Monaten und ich konnte immer noch nicht herausfinden, was diese Technik ist. Offensichtlich gibt es eine Ω(N)
Zeituntergrenze, da wir alle Zahlen mindestens einmal scannen müssen, aber der Interviewer bestand darauf, dass die ZEIT- und RAUM- Komplexität der Lösungstechnik (abzüglich des O(N)
Zeiteingabescans) in k und nicht in N definiert ist .
Die Frage hier ist also einfach:
- Wie würden Sie Q2 lösen ?
- Wie würden Sie Q3 lösen ?
- Wie würden Sie Qk lösen ?
Klarstellungen
- Im Allgemeinen gibt es N Zahlen von 1 .. N , nicht nur 1..100.
- Ich bin nicht auf der Suche nach einer offensichtlichen satzbasierten Lösung, z. B. unter Verwendung eines Bit-Satzes , der das Vorhandensein / Fehlen jeder Zahl durch den Wert eines bestimmten Bits
O(N)
codiert und daher Bits in zusätzlichem Raum verwendet. Wir können uns keinen zusätzlichen Platz leisten, der proportional zu N ist . - Ich bin auch nicht auf der Suche nach dem offensichtlichen Sort-First-Ansatz. Dies und der satzbasierte Ansatz sind in einem Interview erwähnenswert (sie sind einfach zu implementieren und können je nach N sehr praktisch sein). Ich suche nach der Holy Grail-Lösung (die möglicherweise praktisch ist oder nicht, aber dennoch die gewünschten asymptotischen Eigenschaften aufweist).
Natürlich müssen Sie die Eingabe wieder einscannen O(N)
, aber Sie können nur eine kleine Menge an Informationen erfassen (definiert als k, nicht N ) und müssen dann die k fehlenden Zahlen irgendwie finden.
XOR
alle Zahlen von1
bis zun
berechnen und dann das Ergebnis mit allen Zahlen im angegebenen Array zu xoring. Am Ende haben Sie Ihre fehlende Nummer. In dieser Lösung müssen Sie sich nicht wie beim Zusammenfassen um den Überlauf kümmern.Antworten:
Hier ist eine Zusammenfassung des Links von Dimitris Andreou .
Denken Sie an die Summe der i-ten Potenzen, wobei i = 1,2, .., k. Dies reduziert das Problem auf die Lösung des Gleichungssystems
a 1 + a 2 + ... + a k = b 1
a 1 2 + a 2 2 + ... + a k 2 = b 2
...
a 1 k + a 2 k + ... + a k k = b k
Wenn Sie die Newtonschen Identitäten verwenden und b i kennen, können Sie rechnen
c 1 = a 1 + a 2 + ... a k
c 2 = a 1 a 2 + a 1 a 3 + ... + a k-1 a k
...
c k = a 1 a 2 ... a k
Wenn Sie das Polynom (xa 1 ) ... (xa k ) erweitern, sind die Koeffizienten genau c 1 , ..., c k - siehe Viètes Formeln . Da jeder Polynomfaktor eindeutig ist (der Polynomring ist eine euklidische Domäne ), bedeutet dies, dass ein i bis zur Permutation eindeutig bestimmt wird.
Dies beendet einen Beweis dafür, dass das Erinnern an Kräfte ausreicht, um die Zahlen wiederherzustellen. Für die Konstante k ist dies ein guter Ansatz.
Wenn jedoch k variiert, ist der direkte Ansatz der Berechnung von c 1 , ..., c k unerschwinglich teuer, da z. B. c k das Produkt aller fehlenden Zahlen ist, Größe n! / (Nk)!. Um dies zu überwinden, führen Sie Berechnungen im Feld Z q durch , wobei q eine Primzahl ist, so dass n <= q <2n - es existiert nach Bertrands Postulat . Der Beweis muss nicht geändert werden, da die Formeln immer noch gelten und die Faktorisierung von Polynomen immer noch einzigartig ist. Sie benötigen auch einen Algorithmus zur Faktorisierung über endliche Felder, zum Beispiel den von Berlekamp oder Cantor-Zassenhaus .
High-Level-Pseudocode für Konstante k:
Finden Sie zum Variieren von k eine Primzahl n <= q <2n unter Verwendung von z. B. Miller-Rabin und führen Sie die Schritte mit allen Zahlen aus, die modulo q reduziert sind.
EDIT: In der vorherigen Version dieser Antwort wurde angegeben, dass anstelle von Z q , wobei q eine Primzahl ist, ein endliches Feld der Charakteristik 2 verwendet werden kann (q = 2 ^ (log n)). Dies ist nicht der Fall, da Newtons Formeln eine Division durch Zahlen bis k erfordern.
quelle
q = 2^(log n)
. (Wie haben Sie die Super- und Indizes gemachtO(N^2)
Lösung diese Schönheit möglicherweise sogar für ein angemessen hohes Maß übertreffenN
. Lässt mich darüber nachdenken: tinyurl.com/c8fwgw Trotzdem großartige Arbeit! Ich hätte nicht die Geduld gehabt, durch die ganze Mathematik zu kriechen :)hash set
und das Durchlaufen der1...N
Suite mithilfe von Suchvorgängen, um festzustellen, ob Zahlen fehlen, die allgemeinste, im Durchschnitt am schnellsten in Bezug aufk
Variationen, die debuggbarste, wartbarste und verständlichste Lösung wäre. Natürlich ist der mathematische Weg beeindruckend, aber irgendwo auf dem Weg muss man Ingenieur und kein Mathematiker sein. Besonders wenn es ums Geschäft geht.Sie finden es, indem Sie die paar Seiten von Muthukrishnan - Data Stream Algorithms: Puzzle 1: Finding Missing Numbers lesen . Es zeigt genau die Verallgemeinerung, die Sie suchen . Wahrscheinlich hat Ihr Interviewer dies gelesen und warum er diese Fragen gestellt hat.
Wenn nun nur die Leute anfangen würden, die Antworten zu löschen, die von Muthukrishnans Behandlung subsumiert oder ersetzt werden, und diesen Text leichter zu finden machen würden. :) :)
Siehe auch die direkt verwandte Antwort von sdcvvc , die auch Pseudocode enthält (Hurra! Keine Notwendigkeit, diese kniffligen mathematischen Formulierungen zu lesen :)) (danke, großartige Arbeit!).
quelle
Wir können Q2 lösen, indem wir sowohl die Zahlen selbst als auch die Quadrate der Zahlen summieren .
Wir können das Problem dann auf reduzieren
Wo
x
undy
wie weit liegen die Summen unter den erwarteten Werten?Das Ersetzen gibt uns:
Was wir dann lösen können, um unsere fehlenden Zahlen zu bestimmen.
quelle
Wie @j_random_hacker hervorhob, ist dies dem Finden von Duplikaten in O (n) Zeit und O (1) Raum ziemlich ähnlich , und eine Anpassung meiner Antwort dort funktioniert auch hier.
Unter der Annahme, dass der "Beutel" durch ein 1-basiertes
A[]
Größenarray dargestellt wirdN - k
, können wir QkO(N)
zeitlich und zeitlich lösenO(k)
räumlich .Zuerst erweitern wir unser Array
A[]
umk
Elemente, sodass es jetzt die Größe hatN
. Dies ist derO(k)
zusätzliche Platz. Wir führen dann den folgenden Pseudocode-Algorithmus aus:Die erste Schleife initialisiert die
k
zusätzlichen Einträge mit dem ersten Eintrag im Array (dies ist nur ein praktischer Wert, von dem wir wissen, dass er bereits im Array vorhanden ist - nach diesem Schritt alle Einträge, die im anfänglichen Array der Größe fehltenN-k
sind fehlt noch im erweiterten Array).Die zweite Schleife permutiert das erweiterte Array, sodass sich
x
einer dieser Einträge an der Position befindet , wenn das Element mindestens einmal vorhanden istA[x]
.Beachten Sie, dass es zwar eine verschachtelte Schleife hat, aber dennoch ausgeführt wird
O(N)
rechtzeitig ausgeführt wird - ein Swap findet nur statt, wenn es eineni
solchen gibtA[i] != i
, und jeder Swap setzt mindestens ein Element so, dassA[i] == i
, wo dies vorher nicht wahr war. Dies bedeutet, dass die Gesamtzahl der Swaps (und damit die Gesamtzahl der Ausführungen deswhile
Schleifenkörpers) höchstens beträgtN-1
.Die dritte Schleife druckt die Indizes des Arrays
i
, die nicht vom Wert belegt sindi
- dies bedeutet,i
dass sie fehlten.quelle
A[i]
, was bedeutet, dass bei der nächsten Iteration nicht dieselben zwei Werte wie beim vorherigen verglichen werden. Das NeueA[i]
ist das gleiche wie das der letzten SchleifeA[A[i]]
, aber das NeueA[A[i]]
ist ein neuer Wert. Probieren Sie es aus und sehen Sie.Ich habe einen 4-Jährigen gebeten, dieses Problem zu lösen. Er sortierte die Zahlen und zählte dann mit. Dies hat einen Platzbedarf von O (Küchenboden) und funktioniert genauso einfach, auch wenn viele Bälle fehlen.
quelle
Ich bin mir nicht sicher, ob es die effizienteste Lösung ist, aber ich würde alle Einträge durchlaufen und ein Bitset verwenden, um zu merken, welche Zahlen gesetzt sind, und dann auf 0 Bits testen.
Ich mag einfache Lösungen - und ich glaube sogar, dass es schneller sein könnte als die Berechnung der Summe oder der Summe der Quadrate usw.
quelle
O(N)
Zählsortierung noch dieO(N log N)
Vergleichssortierung ist das, wonach ich suche, obwohl beide sehr einfache Lösungen sind.Ich habe die Mathematik nicht überprüft, aber ich vermute, dass das Rechnen
Σ(n^2)
im selben Durchgang, den wir berechnenΣ(n)
, genügend Informationen liefert, um zwei fehlende Zahlen zu erhalten. Tun Sie diesΣ(n^3)
auch, wenn drei vorhanden sind, und so weiter.quelle
Das Problem bei Lösungen, die auf Zahlen basieren, besteht darin, dass sie die Kosten für das Speichern und Arbeiten mit Zahlen mit großen Exponenten nicht berücksichtigen. In der Praxis würde eine Bibliothek mit großen Zahlen verwendet, damit sie für sehr große n funktioniert . Wir können die Raumnutzung für diese Algorithmen analysieren.
Wir können die zeitliche und räumliche Komplexität der Algorithmen von sdcvvc und Dimitris Andreou analysieren.
Lager:
Damit
l_j \in \Theta(j log n)
Insgesamt genutzter Speicher:
\sum_{j=1}^k l_j \in \Theta(k^2 log n)
Verwendeter Speicherplatz: Unter der Annahme, dass das Rechnen Zeit
a^j
brauchtceil(log_2 j)
, Gesamtzeit:Gesamtdauer:
\Theta(kn log n)
Wenn diese Zeit und dieser Raum zufriedenstellend sind, können Sie einen einfachen rekursiven Algorithmus verwenden. Sei b! I der i-te Eintrag in der Tasche, n die Anzahl der Nummern vor dem Umzug und k die Anzahl der Umzüge. In der Haskell-Syntax ...
Verwendeter Speicher:
O(k)
für Liste,O(log(n))
für Stapel:O(k + log(n))
Dieser Algorithmus ist intuitiver, hat dieselbe zeitliche Komplexität und benötigt weniger Speicherplatz.quelle
isInRange
ist O (log n) , nicht O (1) : Es vergleicht Zahlen im Bereich 1..n, also muss es O (log n) Bits vergleichen. Ich weiß nicht, inwieweit sich dieser Fehler auf den Rest der Analyse auswirkt.Warte eine Minute. Wie die Frage schon sagt, befinden sich 100 Nummern in der Tasche. Egal wie groß k ist, das Problem kann in konstanter Zeit gelöst werden, da Sie eine Menge verwenden und Zahlen in höchstens 100-k-Iterationen einer Schleife aus der Menge entfernen können. 100 ist konstant. Der Satz der verbleibenden Zahlen ist Ihre Antwort.
Wenn wir die Lösung auf die Zahlen von 1 bis N verallgemeinern, ändert sich nichts, außer dass N keine Konstante ist, also sind wir in der Zeit O (N - k) = O (N). Wenn wir zum Beispiel einen Bit-Satz verwenden, setzen wir die Bits in O (N) -Zeit auf 1, durchlaufen die Zahlen, setzen die Bits im Laufe der Zeit auf 0 (O (Nk) = O (N)) und dann wir habe die Antwort.
Es scheint mir, dass der Interviewer Sie gefragt hat, wie Sie den Inhalt des endgültigen Satzes in O (k) -Zeit und nicht in O (N) -Zeit ausdrucken sollen . Wenn ein Bit gesetzt ist, müssen Sie natürlich alle N Bits durchlaufen, um zu bestimmen, ob Sie die Nummer drucken sollen oder nicht. Wenn Sie jedoch die Art und Weise ändern, in der das Set implementiert ist, können Sie die Zahlen in k Iterationen ausdrucken. Dazu werden die Zahlen in ein Objekt eingefügt, das sowohl in einem Hash-Set als auch in einer doppelt verknüpften Liste gespeichert werden soll. Wenn Sie ein Objekt aus dem Hash-Set entfernen, entfernen Sie es auch aus der Liste. Die Antworten bleiben in der Liste, die jetzt die Länge k hat.
quelle
Um die Frage nach 2 (und 3) fehlenden Zahlen zu lösen, können Sie Änderungen vornehmen
quickselect
, die im Durchschnitt ausgeführt werdenO(n)
und konstanten Speicher verwenden, wenn die Partitionierung direkt erfolgt.Partitionieren Sie die Menge in Bezug auf einen zufälligen Pivot
p
in Partitionenl
, die Zahlen enthalten, die kleiner als der Pivot sind, undr
die Zahlen enthalten, die größer als der Pivot sind.Bestimmen Sie, in welchen Partitionen sich die 2 fehlenden Zahlen befinden, indem Sie den Pivot-Wert mit der Größe jeder Partition (
p - 1 - count(l) = count of missing numbers in l
undn - count(r) - p = count of missing numbers in r
) vergleichen.a) Wenn jeder Partition eine Nummer fehlt, verwenden Sie den Summenunterschiedsansatz, um jede fehlende Nummer zu finden.
(1 + 2 + ... + (p-1)) - sum(l) = missing #1
und((p+1) + (p+2) ... + n) - sum(r) = missing #2
b) Wenn einer Partition beide Nummern fehlen und die Partition leer ist, sind die fehlenden Nummern entweder
(p-1,p-2)
oder(p+1,p+2)
abhängig davon, auf welcher Partition die Nummern fehlen.Wenn einer Partition 2 Zahlen fehlen, diese aber nicht leer ist, kehren Sie zu dieser Partition zurück.
Mit nur 2 fehlenden Zahlen verwirft dieser Algorithmus immer mindestens eine Partition, sodass er erhalten bleibt
O(n)
durchschnittliche Zeitkomplexität der Schnellauswahl erhalten bleibt. In ähnlicher Weise verwirft dieser Algorithmus bei 3 fehlenden Nummern bei jedem Durchgang mindestens eine Partition (da wie bei 2 fehlenden Nummern höchstens 1 Partition mehrere fehlende Nummern enthält). Ich bin mir jedoch nicht sicher, um wie viel die Leistung abnimmt, wenn weitere fehlende Zahlen hinzugefügt werden.Hier ist eine Implementierung, die keine direkte Partitionierung verwendet, sodass dieses Beispiel den Platzbedarf nicht erfüllt, aber die Schritte des Algorithmus veranschaulicht:
Demo
quelle
Hier ist eine Lösung, die k Bit zusätzlichen Speicherplatzes verwendet, ohne clevere Tricks und einfach. Ausführungszeit O (n), zusätzlicher Raum O (k). Nur um zu beweisen, dass dies gelöst werden kann, ohne zuerst die Lösung zu lesen oder ein Genie zu sein:
quelle
(data [n - 1 - odd] % 2 == 1) ++odd;
?Können Sie überprüfen, ob jede Nummer vorhanden ist? Wenn ja, können Sie dies versuchen:
wenn die fehlenden Zahlen sind
x
undy
dann:Sie überprüfen also den Bereich von
1
bismax(x)
und finden die Nummerquelle
max(x)
bedeutet, wannx
ist eine Zahl?Möglicherweise kann dieser Algorithmus für Frage 1 funktionieren:
Oder noch besser:
Dieser Algorithmus kann tatsächlich für zwei fehlende Zahlen erweitert werden. Der erste Schritt bleibt gleich. Wenn wir GetValue mit zwei fehlenden Nummern aufrufen, sind die beiden fehlenden Nummern das Ergebnis
a1^a2
. Sagen wirval = a1^a2
Um nun a1 und a2 aus val herauszusieben, nehmen wir ein beliebiges gesetztes Bit in val. Nehmen wir an, das
ith
Bit ist in val gesetzt. Das bedeutet, dass a1 und a2 an derith
Bitposition unterschiedliche Paritäten haben . Jetzt führen wir eine weitere Iteration des ursprünglichen Arrays durch und behalten zwei xor-Werte bei. Eine für die Zahlen, bei denen das i-te Bit gesetzt ist, und eine andere, bei der das i-te Bit nicht gesetzt ist. Wir haben jetzt zwei Eimer mit Zahlen, und es wird garantiert, dassa1 and a2
sie in verschiedenen Eimern liegen. Wiederholen Sie nun dasselbe, was wir getan haben, um auf jedem Eimer ein fehlendes Element zu finden.quelle
k=1
, oder? Aber ich benutze gernexor
über Summen, es scheint ein bisschen schneller zu sein.Sie können Q2 lösen, wenn Sie die Summe beider Listen und das Produkt beider Listen haben.
(l1 ist das Original, l2 ist die geänderte Liste)
Wir können dies optimieren, da die Summe einer arithmetischen Reihe das n-fache des Durchschnitts des ersten und letzten Terms beträgt:
Jetzt wissen wir das (wenn a und b die entfernten Zahlen sind):
So können wir neu anordnen zu:
Und multiplizieren:
Und ordnen Sie neu an, so dass die rechte Seite Null ist:
Dann können wir mit der quadratischen Formel lösen:
Beispiel für Python 3-Code:
Ich kenne die Komplexität der Funktionen sqrt, redu und sum nicht, daher kann ich die Komplexität dieser Lösung nicht herausfinden (wenn jemand etwas weiß, kommentieren Sie dies bitte unten.)
quelle
x1*x2*x3*...
?Für Q2 ist dies eine Lösung, die etwas ineffizienter als die anderen ist, aber dennoch eine O (N) -Laufzeit hat und O (k) Speicherplatz beansprucht.
Die Idee ist, den ursprünglichen Algorithmus zweimal auszuführen. In der ersten erhalten Sie eine fehlende Gesamtzahl, wodurch Sie eine Obergrenze der fehlenden Zahlen erhalten. Rufen wir diese Nummer an
N
. Sie wissen, dass sich die fehlenden zwei Zahlen summieren werdenN
, sodass die erste Zahl nur in dem Intervall liegen kann,[1, floor((N-1)/2)]
während die zweite in sein wird[floor(N/2)+1,N-1]
.Auf diese Weise durchlaufen Sie erneut alle Zahlen und verwerfen alle Zahlen, die nicht im ersten Intervall enthalten sind. Diejenigen, die sind, behalten Sie ihre Summe im Auge. Schließlich kennen Sie eine der beiden fehlenden Zahlen und im weiteren Sinne die zweite.
Ich habe das Gefühl, dass diese Methode verallgemeinert werden könnte und möglicherweise mehrere Suchvorgänge während eines einzelnen Durchlaufs über die Eingabe "parallel" ausgeführt werden, aber ich habe noch nicht herausgefunden, wie.
quelle
Ich denke, dies kann ohne komplexe mathematische Gleichungen und Theorien geschehen. Nachfolgend finden Sie einen Vorschlag für eine vorhandene und O (2n) -Zeitkomplexitätslösung:
Annahmen zum Eingabeformular:
Anzahl der Zahlen im Beutel = n
Anzahl fehlender Zahlen = k
Die Zahlen in der Tasche werden durch ein Array der Länge n dargestellt
Länge des Eingabearrays für das Algo = n
Fehlende Einträge im Array (Zahlen aus der Tasche) werden durch den Wert des ersten Elements im Array ersetzt.
Z.B. Anfangs sieht die Tasche aus wie [2,9,3,7,8,6,4,5,1,10]. Wenn 4 herausgenommen wird, wird der Wert 4 zu 2 (dem ersten Element des Arrays). Daher sieht die Tasche nach dem Herausnehmen von 4 wie folgt aus: [2,9,3,7,8,6,2,5,1,10]
Der Schlüssel zu dieser Lösung besteht darin, den INDEX einer besuchten Nummer zu markieren, indem der Wert an diesem INDEX beim Durchlaufen des Arrays negiert wird.
quelle
Es gibt eine allgemeine Möglichkeit, solche Streaming-Algorithmen zu verallgemeinern. Die Idee ist, ein bisschen Randomisierung zu verwenden, um die
k
Elemente hoffentlich in unabhängige Unterprobleme zu "verteilen" , wobei unser ursprünglicher Algorithmus das Problem für uns löst. Diese Technik wird unter anderem bei der Rekonstruktion spärlicher Signale eingesetzt.a
von Größeu = k^2
.h : {1,...,n} -> {1,...,u}
. (Wie Multiplikationsverschiebung )i
in1, ..., n
Erhöhunga[h(i)] += i
x
Dekrementieren Sie für jede Zahl im Eingabestreama[h(x)] -= x
.Wenn alle fehlenden Zahlen in verschiedene Buckets gehasht wurden, enthalten die Nicht-Null-Elemente des Arrays jetzt die fehlenden Zahlen.
Die Wahrscheinlichkeit, dass ein bestimmtes Paar an denselben Bucket gesendet wird, ist geringer als
1/u
per Definition einer universellen Hash-Funktion. Da es ungefährk^2/2
Paare gibt, haben wir, dass die Fehlerwahrscheinlichkeit höchstens istk^2/2/u=1/2
. Das heißt, wir schaffen es mit einer Wahrscheinlichkeit von mindestens 50% und wenn wir zunehmenu
uns erhöhen, wir unsere Chancen.Beachten Sie, dass dieser Algorithmus Platz beansprucht
k^2 logn
(wir benötigenlogn
Bits pro Array-Bucket). Dies entspricht dem Platz, der in der Antwort von @Dimitris Andreou benötigt wird (insbesondere dem Platzbedarf der Polynomfaktorisierung, der zufällig ebenfalls randomisiert wird.) Dieser Algorithmus hat ebenfalls eine Konstante Zeit pro Update, statt Zeitk
bei Leistungssummen.Tatsächlich können wir mit dem in den Kommentaren beschriebenen Trick sogar effizienter als die Leistungssummenmethode sein.
quelle
xor
in jedem Eimer verwenden, anstattsum
, wenn dies auf unserer Maschine schneller ist.k <= sqrt(n)
- zumindest wennu=k^2
? Angenommen, k = 11 und n = 100, dann hätten Sie 121 Buckets und der Algorithmus würde einem Array von 100 Bits ähneln, das Sie abhaken, wenn Sie jedes # aus dem Stream lesen. Das Erhöhenu
verbessert die Erfolgschancen, aber es gibt eine Grenze, um wie viel Sie es erhöhen können, bevor Sie die Platzbeschränkung überschreiten.n
viel größer alsk
, denke ich, aber Sie können tatsächlichk logn
mit einer Methode, die dem beschriebenen Hashing sehr ähnlich ist, Platz sparen, während Sie immer noch konstante Zeitaktualisierungen haben. Es wird in gnunet.org/eppstein-set-reconciliation beschrieben , wie die Methode der Summe der Potenzen, aber im Grunde hasht man 'zwei von k' Buckets mit einer starken Hash-Funktion wie Tabellierungs-Hashing, was garantiert, dass einige Buckets nur ein Element haben . Um zu dekodieren, identifizieren Sie diesen Bucket und entfernen das Element aus beiden Buckets, wodurch (wahrscheinlich) ein weiterer Bucket usw.Eine sehr einfache Lösung für das zweite Quartal, von der ich überrascht bin, dass noch niemand geantwortet hat. Verwenden Sie die Methode aus Q1, um die Summe der beiden fehlenden Zahlen zu ermitteln. Bezeichnen wir es mit S, dann ist eine der fehlenden Zahlen kleiner als S / 2 und die andere größer als S / 2 (duh). Summieren Sie alle Zahlen von 1 bis S / 2 und vergleichen Sie sie mit dem Ergebnis der Formel (ähnlich der Methode in Q1), um die niedrigere Zahl zwischen den fehlenden Zahlen zu ermitteln. Subtrahieren Sie es von S, um die größere fehlende Zahl zu finden.
quelle
Sehr schönes Problem. Ich würde einen festgelegten Unterschied für Qk verwenden. Viele Programmiersprachen unterstützen dies sogar, wie in Ruby:
Es ist wahrscheinlich nicht die effizienteste Lösung, aber es ist eine, die ich im wirklichen Leben verwenden würde, wenn ich in diesem Fall vor einer solchen Aufgabe stünde (bekannte Grenzen, niedrige Grenzen). Wenn die Anzahl sehr groß wäre, würde ich natürlich einen effizienteren Algorithmus in Betracht ziehen, aber bis dahin würde mir die einfache Lösung ausreichen.
quelle
Sie können versuchen, einen Bloom-Filter zu verwenden . Fügen Sie jede Zahl in den Beutel in die Blüte ein und durchlaufen Sie dann den gesamten 1-k-Satz, bis jede nicht gefundene gemeldet wird. Dies findet möglicherweise nicht in allen Szenarien die Antwort, ist jedoch möglicherweise eine ausreichend gute Lösung.
quelle
Ich würde diese Frage anders angehen und den Interviewer auf weitere Details zu dem größeren Problem untersuchen, das er zu lösen versucht. Abhängig vom Problem und den damit verbundenen Anforderungen ist die offensichtliche satzbasierte Lösung möglicherweise das Richtige und der Ansatz, eine Liste zu erstellen und anschließend durchzusuchen, möglicherweise nicht.
Zum Beispiel könnte es sein, dass der Interviewer
n
Nachrichten versendet und wissen mussk
, was nicht zu einer Antwort geführt hat, und dass er es in möglichst kurzer Wanduhrzeit nach dem wissen muss . Danach enthält das Set die Liste der fehlenden Elemente und es ist keine zusätzliche Verarbeitung erforderlich.n-k
Eingang der Antwort . Nehmen wir auch an, dass der Nachrichtenkanal so beschaffen ist, dass selbst bei voller Auslastung genügend Zeit für die Verarbeitung zwischen Nachrichten bleibt, ohne dass sich dies darauf auswirkt, wie lange es dauert, bis das Endergebnis nach dem Eintreffen der letzten Antwort erstellt wird. Diese Zeit kann genutzt werden, um eine identifizierende Facette jeder gesendeten Nachricht in einen Satz einzufügen und sie zu löschen, wenn jede entsprechende Antwort eintrifft. Sobald die letzte Antwort eingetroffen ist, müssen Sie nur noch die Kennung aus dem Satz entfernen, was in typischen Implementierungen erforderlich istO(log k+1)
k
Dies ist sicherlich nicht der schnellste Ansatz für die Stapelverarbeitung vorgenerierter Zahlenbeutel, da das Ganze läuft
O((log 1 + log 2 + ... + log n) + (log n + log n-1 + ... + log k))
. Es funktioniert jedoch für jeden Wert vonk
(auch wenn es nicht im Voraus bekannt ist) und wurde im obigen Beispiel so angewendet, dass das kritischste Intervall minimiert wird.quelle
Sie können die Lösung motivieren, indem Sie sie in Form von Symmetrien (Gruppen, in mathematischer Sprache) betrachten. Unabhängig von der Reihenfolge der Zahlen sollte die Antwort dieselbe sein. Wenn Sie
k
Funktionen verwenden möchten, um die fehlenden Elemente zu ermitteln, sollten Sie sich überlegen, welche Funktionen diese Eigenschaft haben: symmetrisch. Die Funktions_1(x) = x_1 + x_2 + ... + x_n
ist ein Beispiel für eine symmetrische Funktion, aber es gibt andere von höherem Grad. Berücksichtigen Sie insbesondere die elementaren symmetrischen Funktionen . Die elementare symmetrische Funktion des Grades 2 ists_2(x) = x_1 x_2 + x_1 x_3 + ... + x_1 x_n + x_2 x_3 + ... + x_(n-1) x_n
die Summe aller Produkte zweier Elemente. Ähnliches gilt für die elementaren symmetrischen Funktionen des Grades 3 und höher. Sie sind offensichtlich symmetrisch. Darüber hinaus stellt sich heraus, dass sie die Bausteine für alle symmetrischen Funktionen sind.Sie können die elementaren symmetrischen Funktionen erstellen, indem Sie dies beachten
s_2(x,x_(n+1)) = s_2(x) + s_1(x)(x_(n+1))
. Weitere Überlegungen sollten Sie davon überzeugens_3(x,x_(n+1)) = s_3(x) + s_2(x)(x_(n+1))
und so weiter, damit sie in einem Durchgang berechnet werden können.Wie können wir feststellen, welche Elemente im Array fehlten? Denken Sie an das Polynom
(z-x_1)(z-x_2)...(z-x_n)
. Es wird ausgewertet,0
ob Sie eine der Zahlen eingebenx_i
. Wenn Sie das Polynom erweitern, erhalten Siez^n-s_1(x)z^(n-1)+ ... + (-1)^n s_n
. Die elementaren symmetrischen Funktionen erscheinen auch hier, was wirklich keine Überraschung ist, da das Polynom gleich bleiben sollte, wenn wir eine Permutation auf die Wurzeln anwenden.Wir können also das Polynom bauen und versuchen, es zu faktorisieren, um herauszufinden, welche Zahlen nicht in der Menge enthalten sind, wie andere erwähnt haben.
Wenn wir uns schließlich Gedanken über ein Überlaufen des Speichers mit großen Zahlen machen (das n-te symmetrische Polynom liegt in der Größenordnung
100!
), können wir diese Berechnungen durchführen,mod p
wennp
eine Primzahl größer als 100 ist. In diesem Fall bewerten wir das Polynommod p
und stellen fest, dass es erneut ausgewertet wird bis,0
wenn die Eingabe eine Zahl in der Menge ist, und es wird ein Wert ungleich Null ausgewertet, wenn die Eingabe eine Zahl ist, die nicht in der Menge enthalten ist. Wie andere bereits betont haben , müssen wir das Polynom faktorisieren, um die Werte aus dem Polynom in einer Zeit herauszuholen, die davon abhängtk
, und nicht davon .N
mod p
quelle
Ein weiterer Weg ist die Verwendung der Restgraphenfilterung.
Angenommen, wir haben die Nummern 1 bis 4 und 3 fehlt. Die binäre Darstellung ist die folgende:
1 = 001b, 2 = 010b, 3 = 011b, 4 = 100b
Und ich kann ein Flussdiagramm wie das folgende erstellen.
Beachten Sie, dass das Flussdiagramm x Knoten enthält, während x die Anzahl der Bits ist. Und die maximale Anzahl von Kanten beträgt (2 * x) -2.
Für eine 32-Bit-Ganzzahl wird also O (32) oder O (1) benötigt.
Wenn ich nun die Kapazität für jede Zahl ab 1,2,4 entferne, bleibt ein Restdiagramm übrig.
Zum Schluss werde ich eine Schleife wie die folgende ausführen:
Jetzt enthält das Ergebnis
result
Zahlen, die ebenfalls nicht fehlen (falsch positiv). Aber das k <= (Größe des Ergebnisses) <= n, wennk
Elemente fehlen.Ich werde die angegebene Liste ein letztes Mal durchgehen, um zu markieren, ob das Ergebnis fehlt oder nicht.
Die zeitliche Komplexität ist also O (n).
Schließlich ist es möglich , die Anzahl der falsch positiven (und den Raum erforderlich) , indem Knoten zu reduzieren
00
,01
,11
,10
statt nur0
und1
.quelle
Sie müssten wahrscheinlich klären, was O (k) bedeutet.
Hier ist eine triviale Lösung für beliebiges k: Akkumulieren Sie für jedes v in Ihrer Zahlenmenge die Summe von 2 ^ v. Am Ende Schleife i von 1 nach N. Wenn die Summe bitweise UND mit 2 ^ i Null ist, fehlt i. (Oder numerisch, wenn der Boden der Summe geteilt durch 2 ^ i gerade ist. Or
sum modulo 2^(i+1)) < 2^i
.)Einfach richtig? O (N) Zeit, O (1) Speicherung, und es unterstützt beliebiges k.
Abgesehen davon, dass Sie enorme Zahlen berechnen, die auf einem realen Computer jeweils O (N) Speicherplatz benötigen würden. Tatsächlich ist diese Lösung mit einem Bitvektor identisch.
Sie könnten also klug sein und die Summe und die Summe der Quadrate und die Summe der Würfel ... bis zur Summe von v ^ k berechnen und die ausgefallene Mathematik durchführen, um das Ergebnis zu extrahieren. Aber das sind auch große Zahlen, was die Frage aufwirft: Von welchem abstrakten Betriebsmodell sprechen wir? Wie viel passt in den O (1) -Raum und wie lange dauert es, Zahlen beliebiger Größe zusammenzufassen?
quelle
Hier ist eine Lösung, die sich nicht auf komplexe Mathematik stützt wie die Antworten von sdcvvc / Dimitris Andreou, das Eingabearray nicht wie bei caf und Colonel Panic ändert und das Bitset von enormer Größe nicht wie Chris Lercher, JeremyP und verwendet viele andere taten es. Grundsätzlich begann ich mit der Idee von Svalorzen / Gilad Deutch für Q2, verallgemeinerte sie auf den allgemeinen Fall Qk und implementierte sie in Java, um zu beweisen, dass der Algorithmus funktioniert.
Die Idee
Angenommen, wir haben ein beliebiges Intervall I, von dem wir nur wissen, dass es mindestens eine der fehlenden Zahlen enthält. Nach einem Durchgang durch das Eingabearray , wobei wir nur die Zahlen von I betrachten , können wir sowohl die Summe S als auch die Menge Q der fehlenden Zahlen von I erhalten . Wir tun dies, indem wir einfach die Länge von I jedes Mal verringern, wenn wir auf eine Zahl von I stoßen (um Q zu erhalten ) und indem wir die vorberechnete Summe aller Zahlen in I jedes Mal um diese angetroffene Zahl verringern (um S zu erhalten ).
Nun schauen wir uns S und Q an . Wenn Q = 1 , bedeutet dies , dass dann ich enthalten nur eine der fehlenden Zahlen, und diese Zahl ist deutlich S . Wir markieren I als fertig (es wird im Programm als "eindeutig" bezeichnet) und lassen es von weiteren Überlegungen aus. Wenn andererseits Q> 1 ist , können wir den Durchschnitt A = S / Q der in I enthaltenen fehlenden Zahlen berechnen . Da alle Zahlen verschieden sind, wobei mindestens eine dieser Zahlen ist strikt kleiner als A und mindestens ein streng größer als ist A . Jetzt teilen wir mich in A.in zwei kleinere Intervalle, von denen jedes mindestens eine fehlende Zahl enthält. Beachten Sie, dass es keine Rolle spielt, welchem der Intervalle wir A zuweisen, falls es sich um eine Ganzzahl handelt.
Wir machen den nächsten Array-Durchgang, indem wir S und Q für jedes der Intervalle separat berechnen (aber im selben Durchgang) und danach Intervalle mit Q = 1 markieren und Intervalle mit Q> 1 teilen . Wir setzen diesen Prozess fort, bis es keine neuen "mehrdeutigen" Intervalle mehr gibt, dh wir haben nichts zu teilen, da jedes Intervall genau eine fehlende Zahl enthält (und wir kennen diese Zahl immer, weil wir S kennen ). Wir beginnen mit dem einzigen "gesamten Bereich" -Intervall, das alle möglichen Zahlen enthält (wie [1..N] in der Frage).
Zeit- und Raumkomplexitätsanalyse
Die Gesamtzahl der Durchgänge p, die wir machen müssen, bis der Prozess stoppt, ist niemals größer als die Anzahl der fehlenden Zahlen k . Die Ungleichung p <= k kann rigoros bewiesen werden. Andererseits gibt es auch eine empirische Obergrenze p <log 2 N + 3 , die für große Werte von k nützlich ist . Wir müssen eine binäre Suche für jede Nummer des Eingabearrays durchführen, um das Intervall zu bestimmen, zu dem es gehört. Dies addiert den log k- Multiplikator zur Zeitkomplexität.
Insgesamt beträgt die zeitliche Komplexität O (N ≤ min (k, log N) ≤ log k) . Beachten Sie, dass dies für großes k signifikant besser ist als für die Methode von sdcvvc / Dimitris Andreou, die O (N ᛫ k) ist .
Für seine Arbeit benötigt der Algorithmus O (k) zusätzlichen Speicherplatz zum Speichern in den meisten k Intervallen, was in "Bitset" -Lösungen signifikant besser ist als O (N) .
Java-Implementierung
Hier ist eine Java-Klasse, die den obigen Algorithmus implementiert. Es wird immer ein sortiertes Array fehlender Zahlen zurückgegeben. Außerdem müssen die fehlenden Zahlen nicht k gezählt werden, da sie im ersten Durchgang berechnet werden. Der gesamte Zahlenbereich wird durch die Parameter
minNumber
undmaxNumber
angegeben (z. B. 1 und 100 für das erste Beispiel in der Frage).Aus Fairnessgründen erhält diese Klasse Eingaben in Form von
NumberBag
Objekten.NumberBag
Ermöglicht keine Änderung des Arrays und keinen wahlfreien Zugriff und zählt auch, wie oft das Array zum sequentiellen Durchlaufen angefordert wurde. Es ist auch besser für Tests mit großen Arrays geeignet, alsIterable<Integer>
weil es das Boxen primitiverint
Werte vermeidet und das Umwickeln eines Teils eines großenint[]
Werts für eine bequeme Testvorbereitung ermöglicht. Es ist nicht schwer zu ersetzen, falls gewünscht,NumberBag
durchint[]
oderIterable<Integer>
in dem Typfind
Signatur, die von zwei wechselnden for-Schleifen in ihnen in foreach denjenigen.Tests
Nachfolgend finden Sie einfache Beispiele für die Verwendung dieser Klassen.
Tests mit großen Arrays können folgendermaßen durchgeführt werden:
Probieren Sie sie auf Ideone aus
quelle
Ich glaube, ich habe einen
O(k)
Zeit- undO(log(k))
Raumalgorithmus, vorausgesetzt, Sie haben diefloor(x)
undlog2(x)
-Funktionen für beliebig große ganze Zahlen zur Verfügung:Sie haben eine
k
-bit lange Ganzzahl (daher daslog8(k)
Leerzeichen), in der Sie diex^2
Zahl hinzufügen , wobei x die nächste Zahl ist, die Sie in der Tasche finden:s=1^2+2^2+...
Dies brauchtO(N)
Zeit (was für den Interviewer kein Problem ist). Am Ende erhalten Siej=floor(log2(s))
die größte Zahl, die Sie suchen. Danns=s-j
und du machst nochmal das oben genannte:Jetzt haben Sie normalerweise keine
2756
Floor- und Log2-Funktionen für -bit-Ganzzahlen, sondern für Doubles. Damit? Sie können diese Funktionen einfach für jeweils 2 Bytes (oder 1, 3 oder 4) verwenden, um die gewünschten Zahlen zu erhalten. Dies erhöht jedoch dieO(N)
Zeitkomplexitätquelle
Das mag dumm klingen, aber bei dem ersten Problem, das Ihnen präsentiert wird, müssten Sie alle verbleibenden Zahlen in der Tasche sehen, um sie tatsächlich zu addieren und die fehlende Zahl anhand dieser Gleichung zu finden.
Da Sie also alle Zahlen sehen können, suchen Sie einfach nach der fehlenden Zahl. Gleiches gilt, wenn zwei Zahlen fehlen. Ziemlich einfach finde ich. Es macht keinen Sinn, eine Gleichung zu verwenden, wenn Sie die in der Tasche verbleibenden Zahlen sehen.
quelle
Ich denke, das kann so verallgemeinert werden:
Bezeichnen Sie S, M als Anfangswerte für die Summe von arithmetischen Reihen und Multiplikationen.
Ich sollte über eine Formel nachdenken, um dies zu berechnen, aber das ist nicht der Punkt. Wenn eine Nummer fehlt, haben Sie die Lösung bereits bereitgestellt. Wenn jedoch zwei Zahlen fehlen, bezeichnen wir die neue Summe und das Gesamtmultiplikator mit S1 und M1, die wie folgt lauten:
Da Sie S1, M1, M und S kennen, ist die obige Gleichung lösbar, um a und b, die fehlenden Zahlen, zu finden.
Nun zu den drei fehlenden Zahlen:
Jetzt ist Ihr Unbekannter 3, während Sie nur zwei Gleichungen haben, aus denen Sie lösen können.
quelle
M1 = M / (a * b)
(siehe diese Antwort ). Dann funktioniert es gut.Ich weiß nicht, ob dies effizient ist oder nicht, aber ich möchte diese Lösung vorschlagen.
4. Erhalten Sie die Summe der fehlenden Nos mit Ihrem üblichen Ansatz von Summenformel diff und sagen wir, der Diff ist d.
Führen Sie nun eine Schleife aus, um die möglichen Paare (p, q) zu erhalten, die beide in [1, 100] liegen und zu d summieren.
Wenn ein Paar erhalten wird, prüfen Sie, ob (Ergebnis von 3) XOR p = q ist und ob wir fertig sind.
Bitte korrigieren Sie mich, wenn ich falsch liege, und kommentieren Sie auch die zeitliche Komplexität, wenn dies korrekt ist
quelle
Wir können Q1 und Q2 die meiste Zeit in O (log n) ausführen.
Angenommen, unser
memory chip
besteht aus einem Array vonn
Anzahl vontest tubes
. Und eine Zahlx
im Reagenzglas wird durchx
milliliter
chemische Flüssigkeit dargestellt.Angenommen, unser Prozessor ist a
laser light
. Wenn wir den Laser anzünden, durchläuft er alle Röhren senkrecht zu seiner Länge. Jedes Mal, wenn es durch die chemische Flüssigkeit gelangt, wird die Leuchtkraft um verringert1
. Und das Licht bei einer bestimmten Milliliter-Marke zu passieren, ist eine Operation vonO(1)
.Wenn wir nun unseren Laser in der Mitte des Reagenzglases anzünden und die Helligkeitsleistung erhalten
n/2
.n/2
. Wir können auch überprüfen, ob die Leuchtkraft um1
oder verringert ist2
. Wenn es bis dahin reduziert wird,1
ist eine fehlende Zahl kleiner alsn/2
und die andere größer alsn/2
. Wenn es bis dahin reduziert2
ist, sind beide Zahlen kleiner alsn/2
.Wir können den obigen Vorgang immer wieder wiederholen und unsere Problemdomäne eingrenzen. In jedem Schritt verkleinern wir die Domain um die Hälfte. Und schließlich können wir zu unserem Ergebnis gelangen.
Erwähnenswerte parallele Algorithmen (weil sie interessant sind),
O(log^3 n)
. Und dann kann die fehlende Nummer durch binäre SucheO(log n)
rechtzeitig gefunden werden.n
Prozessoren haben, kann theoretisch jeder Prozess eine der Eingaben überprüfen und ein Flag setzen, das die Nummer identifiziert (bequem in einem Array). Und im nächsten Schritt kann jeder Prozess jedes Flag überprüfen und schließlich die Nummer ausgeben, die nicht markiert ist. Der gesamte Prozess wird einigeO(1)
Zeit dauern . Es hat zusätzlichenO(n)
Platz- / Speicherbedarf.Beachten Sie, dass die beiden oben bereitgestellten parallelen Algorithmen möglicherweise zusätzlichen Speicherplatz benötigen, wie im Kommentar erwähnt .
quelle
O(logn)
auf einem Computer zu finden ist.N
, der von und mehr als derO(N)
Zeit (in Bezug auf die Abhängigkeit vonN
) abhängt , was wir besser machen wollen als.