Ich hatte vor einigen Monaten ein Interview mit einer Hedgefondsfirma in New York und leider habe ich das Praktikumsangebot als Daten- / Software-Ingenieur nicht erhalten. (Sie baten auch um die Lösung in Python.)
Ich habe das erste Interviewproblem ziemlich vermasselt ...
Frage: Schreiben Sie bei einer Zeichenfolge von einer Million Zahlen (z. B. Pi) eine Funktion / ein Programm, die alle sich wiederholenden dreistelligen Zahlen und die Anzahl der Wiederholungen größer als 1 zurückgibt
Beispiel: Wenn der String: 123412345123456
wäre, würde die Funktion / das Programm Folgendes zurückgeben:
123 - 3 times
234 - 3 times
345 - 2 times
Sie gaben mir die Lösung nicht, nachdem ich das Interview nicht bestanden hatte, aber sie sagten mir, dass die zeitliche Komplexität für die Lösung konstant 1000 war, da alle möglichen Ergebnisse zwischen:
000 -> 999
Jetzt, wo ich darüber nachdenke, denke ich nicht, dass es möglich ist, einen Algorithmus mit konstanter Zeit zu entwickeln. Ist es?
quelle
They did not give me the solution after I failed the interview, but they did tell me that the time complexity for the solution was constant of 1000 since all the possible outcomes are between: 000 --> 999
Dies war wahrscheinlich der eigentliche Test. Um zu sehen, ob Sie ihnen beweisen können, warum dies nicht möglich ist, und um ihnen die richtige minimale Zeitkomplexität zu zeigen.Antworten:
Sie sind leichtfertig ausgestiegen und möchten wahrscheinlich nicht für einen Hedgefonds arbeiten, bei dem die Quants grundlegende Algorithmen nicht verstehen :-)
Es gibt keine Möglichkeit, eine Datenstruktur beliebiger Größe zu verarbeiten,
O(1)
wenn Sie wie in diesem Fall jedes Element mindestens einmal besuchen müssen. Das Beste, auf das Sie hoffen können, istO(n)
in diesem Fall, won
die Länge der Zeichenfolge ist.Mir scheint, Sie hätten sie auf verschiedene Weise beeindrucken können.
Indem Sie sie zunächst darüber informieren, dass dies nicht möglich ist, es
O(1)
sei denn, Sie verwenden die oben angegebene "verdächtige" Argumentation.Zweitens, indem Sie Ihre Elite-Fähigkeiten unter Beweis stellen, indem Sie Pythonic-Code wie den folgenden bereitstellen:
Dies gibt aus:
Natürlich können Sie das Ausgabeformat nach Belieben ändern.
Und schließlich, wenn man ihnen sagt, dass es mit ziemlicher Sicherheit keine gibt Problem mit einer
O(n)
Lösung, da der obige Code in weniger als einer halben Sekunde Ergebnisse für eine einstellige Zeichenfolge liefert. Es scheint auch ziemlich linear zu skalieren, da eine Zeichenfolge mit 10.000.000 Zeichen 3,5 Sekunden und eine Zeichenfolge mit 100.000.000 Zeichen 36 Sekunden dauert.Und wenn sie Besseres brauchen , gibt es Möglichkeiten, solche Dinge zu parallelisieren, die es erheblich beschleunigen können.
Natürlich nicht innerhalb eines einzelnen Python-Interpreters, aufgrund der GIL, aber Sie könnten die Zeichenfolge in so etwas aufteilen (eine Überlappung
vv
ist erforderlich, um eine ordnungsgemäße Verarbeitung der Grenzbereiche zu ermöglichen):Sie können diese auf separate Mitarbeiter verteilen und die Ergebnisse anschließend kombinieren.
Die Aufteilung der Eingabe und die Kombination der Ausgabe überschwemmen wahrscheinlich jede Einsparung mit kleinen Zeichenfolgen (und möglicherweise sogar Zeichenfolgen mit Millionen Ziffern), aber bei viel größeren Datenmengen kann dies durchaus einen Unterschied bewirken. Hier gilt natürlich mein übliches Mantra "Messen, nicht raten" .
Dieses Mantra gilt auch für andere Möglichkeiten, z. B. die Umgehung von Python insgesamt und die Verwendung einer anderen Sprache, die möglicherweise schneller ist.
Der folgende C-Code, der auf derselben Hardware wie der frühere Python-Code ausgeführt wird, verarbeitet beispielsweise hundert Millionen Ziffern in 0,6 Sekunden, ungefähr so lange, wie der Python-Code eine Million verarbeitet hat . Mit anderen Worten, viel schneller:
quelle
O(1)
wirdn
festgelegt oder begrenzt.N
. Wenn Sie es an der Position in zwei Teile aufteilenN/2
, müssen Sie dennoch berücksichtigen, dass Sie eine gültige dreistellige Übereinstimmung am "Rand" am Endestring1
und am Anfang von verpassen könntenstring2
. Daher müssen Sie Übereinstimmungen zwischenstring1[N/2-2]
undstring2[2]
(unter Verwendung eines auf Null basierenden Index) usw. überprüfen . Das ist die Idee.val -= 100 * (d[i]-'0');
um die führende Ziffer fallen zu lassen.val = 10*val + d[i+2]-'0'
um eine neue niedrigstwertige Ziffer zu akkumulieren (normales String-> Integer-Parsing).val % 100
ist möglicherweise nicht schrecklich, aber nur, wenn100
es sich um eine Konstante zur Kompilierungszeit handelt, sodass keine echte HW-Teilung verwendet wird.Konstante Zeit ist nicht möglich. Alle 1 Million Stellen müssen mindestens einmal betrachtet werden, so dass dies eine zeitliche Komplexität von O (n) ist, wobei in diesem Fall n = 1 Million ist.
Erstellen Sie für eine einfache O (n) -Lösung ein Array der Größe 1000, das die Anzahl der Vorkommen jeder möglichen dreistelligen Zahl darstellt. Stellen Sie jeweils 1 Stelle, den ersten Index == 0, den letzten Index == 999997 und das Inkrementarray [3-stellige Zahl] vor, um ein Histogramm zu erstellen (Anzahl der Vorkommen für jede mögliche 3-stellige Zahl). Geben Sie dann den Inhalt des Arrays mit einer Anzahl> 1 aus.
quelle
x-'0'
Muster ist in Python jedoch nicht gültig, es ist ein C-Ismus (wobei Zeichen Ganzzahlen sind).Eine Million ist klein für die Antwort, die ich unten gebe. Erwarten Sie nur, dass Sie in der Lage sein müssen, die Lösung im Interview ohne Pause auszuführen. Dann funktioniert Folgendes in weniger als zwei Sekunden und liefert das erforderliche Ergebnis:
Hoffentlich würde der Interviewer nach der Verwendung der Standardbibliothekssammlungen suchen. Gegenklasse.
Parallele Ausführungsversion
Ich habe einen Blog-Beitrag dazu mit mehr Erklärungen geschrieben.
quelle
O(1)
.Die einfache O (n) -Lösung wäre, jede dreistellige Zahl zu zählen:
Dies würde 1000 Mal alle 1 Million Ziffern durchsuchen.
Die Ziffern nur einmal durchlaufen:
Das Timing zeigt, dass die Iteration nur einmal über den Index doppelt so schnell ist wie die Verwendung
count
.quelle
text.count()
?text.count
dies in einer kompilierten Hochgeschwindigkeitssprache (z. B. C) erfolgt, im Gegensatz zu langsamen, auf Python-Ebene interpretierten Schleifen, gibt es einen Rabatt.count
ist falsch, da überlappende Muster nicht berücksichtigt werden. Beachten Sie, dass,'111'.count('11') == 1
wenn wir es erwarten würden2
.O(n)
Lösung“ ist eigentlichO(10**d * n)
mitd
der Anzahl gesuchter Ziffern undn
der Gesamtlänge des Strings. Der zweite istO(n)
Zeit undO(10**d + n)
Raum.Hier ist eine NumPy-Implementierung des "Consensus" O (n) -Algorithmus: Gehen Sie alle Triplets und Bin durch, während Sie gehen. Das Binning erfolgt, indem Sie auf "385" stoßen und eins zu bin [3, 8, 5] hinzufügen, was eine O (1) -Operation ist. Die Behälter sind in einem
10x10x10
Würfel angeordnet . Da das Binning vollständig vektorisiert ist, enthält der Code keine Schleife.Es überrascht nicht, dass NumPy bei großen Datenmengen etwas schneller ist als die reine Python-Lösung von @ Daniel. Beispielausgabe:
quelle
ndarray
Bei s, dem Kerntyp, geht es um die effiziente Speicherung, Bearbeitung und Indizierung mehrdimensionaler Zahlenfelder. Manchmal können Sie sich durch Abflachen um einige Prozent rasieren, aber in diesem Fall bringt es Ihnen nicht viel, wenn Sie 100 x [0] + 10 x [1] + x [2] von Hand ausführen. Ich habe den verwendet, von dem @Daniel sagte, er sei schneller. Sie können den Benchmark-Code selbst überprüfen.Ich würde das Problem wie folgt lösen:
Auf Ihre Beispielzeichenfolge angewendet ergibt dies:
Diese Lösung läuft in O (n), wobei n die Länge der bereitgestellten Zeichenfolge ist, und ich denke, sie ist die beste, die Sie bekommen können.
quelle
Counter
. Sie benötigen keinefinal_dict
und müssen diese nicht bei jeder Iteration aktualisieren.Nach meinem Verständnis können Sie die Lösung nicht in einer konstanten Zeit haben. Es dauert mindestens einen Durchgang über die millionenstellige Zahl (vorausgesetzt, es handelt sich um eine Zeichenfolge). Sie können eine dreistellige fortlaufende Iteration über die Ziffern der Millionenlänge durchführen und den Wert des Hash-Schlüssels um 1 erhöhen, wenn er bereits vorhanden ist, oder einen neuen Hash-Schlüssel (initialisiert durch den Wert 1) erstellen, wenn er noch nicht vorhanden ist das Wörterbuch.
Der Code sieht ungefähr so aus:
Sie können bis zu den Schlüsseln filtern, deren Elementwert größer als 1 ist.
quelle
Wie in einer anderen Antwort erwähnt, können Sie diesen Algorithmus nicht in konstanter Zeit ausführen, da Sie mindestens n Ziffern betrachten müssen. Die lineare Zeit ist die schnellste, die Sie bekommen können.
Der Algorithmus kann jedoch im O (1) -Raum durchgeführt werden . Sie müssen nur die Anzahl jeder dreistelligen Nummer speichern, sodass Sie ein Array mit 1000 Einträgen benötigen. Sie können die Nummer dann streamen.
Ich vermute, dass entweder der Interviewer falsch geschrieben hat, als er Ihnen die Lösung gegeben hat, oder dass Sie "konstante Zeit" falsch gehört haben, als er "konstanter Raum" sagte.
quelle
O(10**d)
zusätzlichen Platz, wobeid
die Anzahl der Dezimalstellen angegeben wird , nach denen Sie suchen.Hier ist meine Antwort:
Die Array-Suchmethode ist sehr schnell (sogar schneller als die Numpy-Methode von @ paul-panzer!). Natürlich betrügt es, da es nach Abschluss nicht technisch fertig ist, weil es einen Generator zurückgibt. Es muss auch nicht jede Iteration überprüft werden, ob der Wert bereits vorhanden ist, was wahrscheinlich sehr hilfreich ist.
quelle
Counters
werden nicht so verwendet. Bei richtiger Anwendung werden sie mit Ihrem Beispiel zur schnellsten Option. Wenn Sietimeit
eine Liste mit einem Generator verwenden, wird Ihre Methode langsamer alsCounter
oderdict
. Siehe hier .f_array
könnten Sie schneller sein, wenn Sie zuerst jedes Zeichen in ein int konvertierenints = [int(c) for c in text]
und dann verwendeni, j, k = ints[n:n+3]
.Bild als Antwort:
Sieht aus wie ein Schiebefenster.
quelle
Hier ist meine Lösung:
Mit ein wenig Kreativität in der for-Schleife (und einer zusätzlichen Suchliste mit True / False / None zum Beispiel) sollten Sie in der Lage sein, die letzte Zeile loszuwerden, da Sie nur Schlüssel in Diktaten erstellen möchten, die wir bis zu diesem Zeitpunkt einmal besucht haben . Ich hoffe es hilft :)
quelle
- Erzählen aus der Perspektive von C. - Sie können int-3-d-Array-Ergebnisse erzielen [10] [10] [10]; -Gehen Sie von der 0. Position zur n-4. Position, wobei n die Größe des String-Arrays ist. -Überprüfen Sie an jedem Ort den aktuellen, den nächsten und den nächsten. -Inkrementiere den cntr als resutls [current] [next] [next's next] ++; -Drucken Sie die Werte von
-Es ist O (n) Zeit, es gibt keine Vergleiche. -Sie können hier einige parallele Dinge ausführen, indem Sie das Array partitionieren und die Übereinstimmungen um die Partitionen berechnen.
quelle
quelle