Angenommen, wir erhalten ein Array von n ganzen Zahlen, die die Aktienkurse an einem einzelnen Tag darstellen. Wir wollen ein Paar finden (buyDay, sellDay) , mit buyDay ≤ sellDay , so dass , wenn wir den Bestand an gekauft buyDay und verkaufen es an sellDay , würden wir unseren Gewinn maximieren.
Es ist klar, dass es eine O (n 2 ) -Lösung für den Algorithmus gibt, indem alle möglichen Paare (buyDay, sellDay) ausprobiert und das Beste aus allen herausgeholt werden. Gibt es jedoch einen besseren Algorithmus, der möglicherweise in O (n) -Zeit ausgeführt wird?
arrays
algorithm
big-o
time-complexity
Ajeet Ganga
quelle
quelle
Antworten:
Ich liebe dieses Problem. Es ist eine klassische Interviewfrage und je nachdem, wie Sie darüber denken, erhalten Sie immer bessere Lösungen. Es ist sicherlich möglich, dies in einer besseren Zeit als O (n 2 ) zu tun , und ich habe drei verschiedene Möglichkeiten aufgelistet, wie Sie hier über das Problem nachdenken können. Hoffentlich beantwortet dies Ihre Frage!
Erstens die Divide-and-Conquer-Lösung. Mal sehen, ob wir dies lösen können, indem wir die Eingabe in zwei Hälften teilen, das Problem in jedem Subarray lösen und dann beide miteinander kombinieren. Es stellt sich heraus, dass wir dies tatsächlich und effizient tun können! Die Intuition ist wie folgt. Wenn wir einen einzigen Tag haben, ist es die beste Option, an diesem Tag zu kaufen und ihn dann am selben Tag ohne Gewinn zurück zu verkaufen. Andernfalls teilen Sie das Array in zwei Hälften. Wenn wir darüber nachdenken, wie die optimale Antwort aussehen könnte, muss sie an einer von drei Stellen erfolgen:
Wir können die Werte für (1) und (2) erhalten, indem wir unseren Algorithmus in der ersten und zweiten Hälfte rekursiv aufrufen. Für Option (3) besteht der Weg, den höchsten Gewinn zu erzielen, darin, am niedrigsten Punkt in der ersten Hälfte zu kaufen und am größten Punkt in der zweiten Hälfte zu verkaufen. Wir können die Minimal- und Maximalwerte in den beiden Hälften finden, indem wir einfach einen einfachen linearen Scan über die Eingabe durchführen und die beiden Werte finden. Dies gibt uns dann einen Algorithmus mit der folgenden Wiederholung:
Wenn wir den Master-Satz verwenden , um die Wiederholung zu lösen, stellen wir fest, dass dies in O (n lg n) -Zeit ausgeführt wird und O (lg n) -Raum für die rekursiven Aufrufe verwendet. Wir haben gerade die naive O (n 2 ) -Lösung geschlagen!
Aber warte! Wir können es viel besser machen. Beachten Sie, dass der einzige Grund, warum wir in unserer Wiederholung einen O (n) -Term haben, darin besteht, dass wir die gesamte Eingabe scannen mussten, um die minimalen und maximalen Werte in jeder Hälfte zu finden. Da wir bereits jede Hälfte rekursiv untersuchen, können wir es vielleicht besser machen, wenn die Rekursion auch die in jeder Hälfte gespeicherten Minimal- und Maximalwerte zurückgibt! Mit anderen Worten, unsere Rekursion gibt drei Dinge zurück:
Diese beiden letzten Werte können rekursiv mit einer einfachen Rekursion berechnet werden, die wir gleichzeitig mit der zu berechnenden Rekursion ausführen können (1):
Wenn wir diesen Ansatz verwenden, ist unsere Wiederholungsbeziehung jetzt
Die Verwendung des Master-Theorems hier gibt uns eine Laufzeit von O (n) mit O (lg n) Raum, was sogar besser ist als unsere ursprüngliche Lösung!
Aber Moment mal - wir können es noch besser machen! Lassen Sie uns darüber nachdenken, dieses Problem mithilfe der dynamischen Programmierung zu lösen. Die Idee wird sein, über das Problem wie folgt nachzudenken. Angenommen, wir kennen die Antwort auf das Problem, nachdem wir uns die ersten k Elemente angesehen haben. Könnten wir unser Wissen über das (k + 1) st-Element in Kombination mit unserer ursprünglichen Lösung nutzen, um das Problem für die ersten (k + 1) Elemente zu lösen? Wenn ja, könnten wir einen großartigen Algorithmus in Gang bringen, indem wir das Problem für das erste Element, dann die ersten zwei, dann die ersten drei usw. lösen, bis wir es für die ersten n Elemente berechnet haben.
Lassen Sie uns darüber nachdenken, wie das geht. Wenn wir nur ein Element haben, wissen wir bereits, dass es das beste Kauf / Verkauf-Paar sein muss. Nehmen wir nun an, wir kennen die beste Antwort für die ersten k Elemente und betrachten das (k + 1) st-Element. Der einzige Weg, wie dieser Wert eine bessere Lösung als die für die ersten k Elemente hatte, besteht darin, dass der Unterschied zwischen dem kleinsten der ersten k Elemente und diesem neuen Element größer ist als der größte Unterschied, den wir bisher berechnet haben. Nehmen wir also an, wir verfolgen beim Durchlaufen der Elemente zwei Werte - den minimalen Wert, den wir bisher gesehen haben, und den maximalen Gewinn, den wir mit nur den ersten k Elementen erzielen können. Anfangs ist der minimale Wert, den wir bisher gesehen haben, das erste Element, und der maximale Gewinn ist Null. Wenn wir ein neues Element sehen, Wir aktualisieren zunächst unseren optimalen Gewinn, indem wir berechnen, wie viel wir verdienen würden, indem wir zum niedrigsten bisher gesehenen Preis kaufen und zum aktuellen Preis verkaufen. Wenn dies besser ist als der optimale Wert, den wir bisher berechnet haben, aktualisieren wir die optimale Lösung, um diesen neuen Gewinn zu erzielen. Als nächstes aktualisieren wir das bisher gesehene Minimum-Element so, dass es das Minimum des aktuell kleinsten Elements und des neuen Elements ist.
Da wir bei jedem Schritt nur O (1) arbeiten und jedes der n Elemente genau einmal besuchen, dauert es O (n), bis der Vorgang abgeschlossen ist! Darüber hinaus wird nur O (1) -Hilfsspeicher verwendet. Das ist so gut wie wir es bisher bekommen haben!
Als Beispiel für Ihre Eingaben sehen Sie hier, wie dieser Algorithmus ausgeführt werden kann. Die Zahlen zwischen den einzelnen Werten des Arrays entsprechen den Werten, die der Algorithmus an diesem Punkt hält. Sie würden nicht alle diese speichern (es würde O (n) Speicher benötigen!), Aber es ist hilfreich zu sehen, wie sich der Algorithmus weiterentwickelt:
Antwort: (5, 10)
Antwort: (4, 12)
Antwort: (1, 5)
Können wir es jetzt besser machen? Leider nicht im asymptotischen Sinne. Wenn wir weniger als O (n) Zeit verwenden, können wir nicht alle Zahlen auf großen Eingaben betrachten und können daher nicht garantieren, dass wir die optimale Antwort nicht verpassen (wir könnten sie einfach in den Elementen "verstecken", die wir haben habe nicht angeschaut). Außerdem können wir nicht weniger als O (1) Speicherplatz verwenden. Möglicherweise gibt es einige Optimierungen für die in der Big-O-Notation verborgenen konstanten Faktoren, aber ansonsten können wir keine radikal besseren Optionen erwarten.
Insgesamt bedeutet dies, dass wir die folgenden Algorithmen haben:
Hoffe das hilft!
BEARBEITEN : Wenn Sie interessiert sind, habe ich eine Python-Version dieser vier Algorithmen codiert, damit Sie mit ihnen herumspielen und ihre relativen Leistungen beurteilen können. Hier ist der Code:
quelle
Dies ist das maximale Summen-Subsequenzproblem mit ein wenig Indirektion. Das maximale Summen-Teilsequenzproblem erhält eine Liste von ganzen Zahlen, die positiv oder negativ sein können. Finden Sie die größte Summe einer zusammenhängenden Teilmenge dieser Liste.
Sie können dieses Problem trivial in dieses Problem umwandeln, indem Sie den Gewinn oder Verlust zwischen aufeinanderfolgenden Tagen mitnehmen. Sie würden also eine Liste von Aktienkursen, z. B.
[5, 6, 7, 4, 2]
in eine Liste von Gewinnen / Verlusten, z[1, 1, -3, -2]
. Das Subsequenzsummenproblem ist dann ziemlich einfach zu lösen: Finden Sie die Subsequenz mit der größten Summe von Elementen in einem Arrayquelle
Ich bin mir nicht sicher, warum dies als dynamische Programmierfrage angesehen wird. Ich habe diese Frage in Lehrbüchern und Algorithmushandbüchern gesehen, die O (n log n) Laufzeit und O (log n) für Speicherplatz verwenden (z. B. Elemente von Programmierinterviews). Es scheint ein viel einfacheres Problem zu sein, als die Leute es sich vorstellen.
Dies funktioniert, indem der maximale Gewinn, der minimale Kaufpreis und folglich der optimale Kauf- / Verkaufspreis verfolgt werden. Beim Durchlaufen jedes Elements im Array wird überprüft, ob das angegebene Element kleiner als der Mindestkaufpreis ist. Wenn dies der
min
Fall ist, wird der Mindestkaufpreisindex ( ) aktualisiert, um der Index dieses Elements zu sein. ZusätzlichbecomeABillionaire
prüft der Algorithmus für jedes Element, obarr[i] - arr[min]
(die Differenz zwischen dem aktuellen Element und dem Mindestkaufpreis) größer als der aktuelle Gewinn ist. Wenn dies der Fall ist, wird der Gewinn auf diese Differenz aktualisiert und Kaufarr[min]
und Verkauf werden auf gesetztarr[i]
.Läuft in einem einzigen Durchgang.
Co-Autor: https://stackoverflow.com/users/599402/ephraim
quelle
Das Problem ist identisch mit der maximalen Teilsequenz, die
ich mithilfe der dynamischen Programmierung gelöst habe. Verfolgen Sie den aktuellen und den vorherigen Wert (Gewinn, Kauf- und Verkaufsdatum). Wenn der aktuelle Wert höher als der vorherige ist, ersetzen Sie den vorherigen durch den aktuellen.
quelle
Hier ist meine Java-Lösung:
quelle
Ich habe eine einfache Lösung gefunden - Code ist eher selbsterklärend. Es ist eine dieser dynamischen Programmierfragen.
Der Code kümmert sich nicht um Fehlerprüfung und Randfälle. Es ist nur ein Beispiel, um die Idee einer grundlegenden Logik zur Lösung des Problems zu vermitteln.
quelle
Hier ist meine Lösung. Ändert den maximalen Subsequenzalgorithmus. Löst das Problem in O (n). Ich denke, es geht nicht schneller.
quelle
Dies ist ein interessantes Problem, denn es scheint hart, aber sorgfältige Überlegung ergibt eine elegante, abgespeckte Lösung.
Wie bereits erwähnt, kann Brute-Force in O (N ^ 2) -Zeit gelöst werden. Durchlaufen Sie für jeden Eintrag im Array (oder in der Liste) alle vorherigen Einträge, um das Minimum oder Maximum zu erhalten, je nachdem, ob das Problem darin besteht, den größten Gewinn oder Verlust zu finden.
So denken Sie über eine Lösung in O (N) nach: Jeder Eintrag repräsentiert ein neues mögliches Maximum (oder Minimum). Dann müssen wir nur noch das vorherige min (oder max) speichern und das Diff mit dem aktuellen und dem vorherigen min (oder max) vergleichen. Kinderleicht.
Hier ist der Code in Java als JUnit-Test:
Bei der Berechnung des größten Verlusts behalten wir das Maximum in der Liste (Kaufpreis) bis zum aktuellen Eintrag im Auge. Wir berechnen dann den Unterschied zwischen dem Maximum und dem aktuellen Eintrag. Wenn max - current> maxLoss, behalten wir diesen Unterschied als neuen maxLoss bei. Da der Index von max garantiert unter dem aktuellen Index liegt, garantieren wir, dass das Kaufdatum unter dem Verkaufsdatum liegt.
Bei der Berechnung des größten Gewinns wird alles umgekehrt. Wir verfolgen die min in der Liste bis zum aktuellen Eintrag. Wir berechnen den Unterschied zwischen dem min und dem aktuellen Eintrag (Umkehrung der Reihenfolge in der Subtraktion). Wenn current - min> maxGain, behalten wir diesen Unterschied als neuen maxGain bei. Auch hier steht der Index des "Kaufs" (min) vor dem Index des aktuellen ("Verkaufen").
Wir müssen nur den maxGain (oder maxLoss) und den Index von min oder max verfolgen, aber nicht beide, und wir müssen keine Indizes vergleichen, um zu bestätigen, dass "Kaufen" weniger ist als "Verkaufen", da wir bekomme das natürlich.
quelle
Maximaler Einzelverkaufsgewinn, O (n) -Lösung
Hier ist ein Projekt, das Zeitkomplexitätstests für o (N) vs o (n ^ 2) -Ansätze für einen zufälligen Datensatz mit 100.000 Ints durchführt. O (n ^ 2) dauert 2 Sekunden, während O (n) 0,01 Sekunden dauert
https://github.com/gulakov/complexity.js
Dies ist der langsamere o (n ^ 2) -Ansatz, der den Rest der Tage für jeden Tag durchläuft, eine doppelte Schleife.
quelle
Die Antwort mit der höchsten Bewertung berücksichtigt keine Fälle, in denen der maximale Gewinn negativ ist, und sollte geändert werden, um solche Fälle zu berücksichtigen. Man kann dies tun, indem man den Bereich der Schleife auf (len (a) - 1) begrenzt und die Art und Weise ändert, wie der Gewinn bestimmt wird, indem der Index um eins verschoben wird.
Vergleichen Sie diese Version der Funktion mit der vorherigen für das Array:
quelle
quelle
Eine Möglichkeit, den maximalen Gewinn zu bestimmen, könnte darin bestehen, die linken minimalen und rechten maximalen Elemente im Array an jedem Index im Array zu verfolgen. Wenn Sie dann die Aktienkurse durchlaufen, kennen Sie für jeden Tag den niedrigsten Preis bis zu diesem Tag und den Höchstpreis nach (und einschließlich) diesem Tag.
Definieren wir zum Beispiel a
min_arr
undmax_arr
mit dem angegebenen Arrayarr
. Indexi
inmin_arr
wäre das Mindestelement inarr
für alle Indizes<= i
(links von und einschließlich i). Indexi
inmax_arr
wäre das maximale Element inarr
für alle Indizes>= i
(Recht von und einschließlich i). Dann könnten Sie die maximale Differenz zwischen den entsprechenden Elementen inmax_arr
und `min_arr 'finden:Dies sollte in O (n) Zeit laufen, aber ich glaube, es verbraucht viel Platz.
quelle
Dies ist der maximale Unterschied zwischen zwei Elementen im Array und dies ist meine Lösung:
O (N) Zeitkomplexität O (1) Raumkomplexität
quelle
Nachdem ich dies in einer Live-Codierungsprüfung für eine Position als FB-Lösungsingenieur nicht bestanden hatte, musste ich es in einer ruhigen, kühlen Atmosphäre lösen. Hier sind meine 2 Cent:
quelle
Die einzige Antwort, die die Frage wirklich beantwortet, ist die von @akash_magoon (und das auf so einfache Weise!), Gibt jedoch nicht das genaue Objekt zurück, das in der Frage angegeben ist. Ich habe ein bisschen umgestaltet und meine Antwort in PHP gibt genau das zurück, was gefragt wird:
quelle
Eine saubere Lösung:
quelle
Dieses Programm in Python3 kann den Kauf- und Verkaufspreis zurückgeben, der den Gewinn maximiert, berechnet mit der Zeitkomplexität von O (n) und der Raumkomplexität von O (1) .
quelle
Hier ist meine Lösung
quelle
Für alle Antworten, die die minimalen und maximalen Elemente verfolgen, ist diese Lösung tatsächlich eine O (n ^ 2) -Lösung. Dies liegt daran, dass am Ende überprüft werden muss, ob das Maximum nach dem Minimum aufgetreten ist oder nicht. Falls dies nicht der Fall ist, sind weitere Iterationen erforderlich, bis diese Bedingung erfüllt ist, und dies führt zu einem Worst-Case von O (n ^ 2). Und wenn Sie die zusätzlichen Iterationen überspringen möchten, ist viel mehr Speicherplatz erforderlich. In jedem Fall ein Nein-Nein im Vergleich zur dynamischen Programmierlösung
quelle