Ich habe eine Reihe von ganzen Zahlen. Ich möchte die am längsten zunehmende Teilsequenz dieses Satzes mithilfe dynamischer Programmierung finden.
215
Ich habe eine Reihe von ganzen Zahlen. Ich möchte die am längsten zunehmende Teilsequenz dieses Satzes mithilfe dynamischer Programmierung finden.
Antworten:
OK, ich werde zuerst die einfachste Lösung beschreiben, die O (N ^ 2) ist, wobei N die Größe der Sammlung ist. Es gibt auch eine O (N log N) -Lösung, die ich auch beschreiben werde. Suchen Sie hier im Abschnitt Effiziente Algorithmen.
Ich gehe davon aus, dass die Indizes des Arrays von 0 bis N - 1 sind. Definieren
DP[i]
wir also die Länge des LIS (am längsten ansteigende Teilsequenz), das bei Element mit Index endeti
. Zur BerechnungDP[i]
schauen wir uns alle Indizes anj < i
und prüfen, obDP[j] + 1 > DP[i]
undarray[j] < array[i]
(wir wollen, dass sie zunehmen). Wenn dies zutrifft, können wir das aktuelle Optimum für aktualisierenDP[i]
. Um das globale Optimum für das Array zu finden, können Sie den Maximalwert von übernehmenDP[0...N - 1]
.Ich benutze das Array
prev
, um später die tatsächliche Sequenz nicht nur ihre Länge finden zu können. Gehen Sie einfach rekursiv vonbestEnd
in einer Schleife zurück mitprev[bestEnd]
. Der-1
Wert ist ein Zeichen zum Stoppen.OK, jetzt zur effizienteren
O(N log N)
Lösung:Sei
S[pos]
definiert als die kleinste ganze Zahl, die eine zunehmende Längenfolge beendetpos
. Durchlaufen Sie nun jede GanzzahlX
des Eingabesatzes und gehen Sie wie folgt vor:Wenn
X
> letztes Element inS
, dannX
an das Ende von anhängenS
. Dies bedeutet, dass wir einen neuen größten gefunden habenLIS
.Suchen Sie andernfalls das kleinste Element in
S
, das>=
als istX
, und ändern Sie es inX
. DaS
das Element jederzeit sortiert ist, kann es mithilfe der binären Suche in gefunden werdenlog(N)
.Gesamtlaufzeit -
N
Ganzzahlen und eine binäre Suche für jede von ihnen - N * log (N) = O (N log N)Lassen Sie uns nun ein reales Beispiel machen:
Sammlung von ganzen Zahlen:
2 6 3 4 1 2 9 5 8
Schritte:
Die Länge des LIS ist also
5
(die Größe von S).Um das tatsächliche zu rekonstruieren, verwenden
LIS
wir wieder ein übergeordnetes Array. Seiparent[i]
der Vorgänger von Element mit Indexi
in derLIS
Endung bei Element mit Indexi
.Zur Vereinfachung können wir
S
nicht die tatsächlichen Ganzzahlen, sondern deren Indizes (Positionen) in der Menge im Array behalten . Wir behalten nicht{1, 2, 4, 5, 8}
, aber behalten{4, 5, 3, 7, 8}
.Das heißt, Eingang [4] = 1 , Eingang [5] = 2 , Eingang [3] = 4 , Eingang [7] = 5 , Eingang [8] = 8 .
Wenn wir das übergeordnete Array ordnungsgemäß aktualisieren, lautet das tatsächliche LIS:
Nun zum Wichtigen: Wie aktualisieren wir das übergeordnete Array? Es gibt zwei Möglichkeiten:
Wenn
X
> letztes Element inS
, dannparent[indexX] = indexLastElement
. Dies bedeutet, dass das übergeordnete Element des neuesten Elements das letzte Element ist. Wir stellen uns nurX
bis zum Ende vorS
.Finden sonst den Index des kleinsten Elements in
S
, was>=
alsX
, und ändern Sie ihn aufX
. Hierparent[indexX] = S[index - 1]
.quelle
DP[j] + 1 == DP[i]
dannDP[i]
nicht besser wird mitDP[i] = DP[j] + 1
. Wir versuchen zu optimierenDP[i]
.[1,2,5,8]
: 4 kommt vor 1 im Array. Wie kann das LIS sein[1,2,4,5,8]
?[2,3,4,5,8]
. Lesen Sie sorgfältig - dasS
ArrayDOES NOT
repräsentiert eine tatsächliche Sequenz.Let S[pos] be defined as the smallest integer that ends an increasing sequence of length pos.
Die Erklärung von Petar Minchev hat mir geholfen, die Dinge zu klären, aber es war schwierig für mich, zu analysieren, was alles war, und so habe ich eine Python-Implementierung mit übermäßig beschreibenden Variablennamen und vielen Kommentaren erstellt. Ich habe eine naive rekursive Lösung gemacht, die O (n ^ 2) -Lösung und die O (n log n) -Lösung.
Ich hoffe, es hilft, die Algorithmen zu klären!
Die rekursive Lösung
Die dynamische Programmierlösung O (n ^ 2)
Die dynamische Programmierlösung O (n log n)
quelle
bisect
. Um zu demonstrieren, wie ein Algorithmus funktioniert und welche Leistungsmerkmale er aufweist, habe ich versucht, die Dinge so primitiv wie möglich zu halten.Als ich über die DP-Lösung sprach, fand ich es überraschend, dass niemand die Tatsache erwähnte, dass LIS auf LCS reduziert werden kann . Alles, was Sie tun müssen, ist die Kopie der Originalsequenz zu sortieren, alle Duplikate zu entfernen und LCS von ihnen zu machen. Im Pseudocode ist es:
Und die vollständige Implementierung in Go geschrieben. Sie müssen nicht die gesamte n ^ 2 DP-Matrix pflegen, wenn Sie die Lösung nicht rekonstruieren müssen.
quelle
Die folgende C ++ - Implementierung enthält auch Code, der mithilfe eines aufgerufenen Arrays die tatsächlich am längsten ansteigende Teilsequenz erstellt
prev
.Implementierung ohne Stapel kehren Sie einfach den Vektor um
quelle
Hier sind drei Schritte zur Bewertung des Problems unter dem Gesichtspunkt der dynamischen Programmierung:
Wenn wir als Beispiel die Sequenz {0, 8, 2, 3, 7, 9} am Index nehmen:
Hier ist der funktionierende C ++ 11-Code:
quelle
Hier ist eine Scala-Implementierung des O (n ^ 2) -Algorithmus:
quelle
Hier ist eine weitere O (n ^ 2) JAVA-Implementierung. Keine Rekursion / Memoisierung zur Erzeugung der eigentlichen Teilsequenz. Nur ein String-Array, in dem das tatsächliche LIS in jeder Phase gespeichert wird, und ein Array, in dem die Länge des LIS für jedes Element gespeichert wird. Ziemlich einfach. Guck mal:
Code in Aktion: http://ideone.com/sBiOQx
quelle
Dies kann in O (n ^ 2) mit dynamischer Programmierung gelöst werden. Python-Code für das gleiche wäre wie: -
Zur Eingabe:
5 19 5 81 50 28 29 1 83 23
Ausgabe wäre:
[1, 2, 1, 3, 3, 3, 4, 1, 5, 3] 5
Der Listenindex der Ausgabeliste ist der Listenindex der Eingabeliste. Der Wert an einem bestimmten list_index in der Ausgabeliste gibt die am längsten zunehmende Teilsequenzlänge für diesen list_index an.
quelle
Hier ist Java O (nlogn) Implementierung
quelle
Dies ist eine Java-Implementierung in O (n ^ 2). Ich habe die binäre Suche einfach nicht verwendet, um das kleinste Element in S zu finden, das> = als X ist. Ich habe nur eine for-Schleife verwendet. Die Verwendung der binären Suche würde die Komplexität bei O (n logn) erhöhen.
quelle
Überprüfen Sie den Code in Java auf die am längsten zunehmende Teilsequenz mit den Array-Elementen
http://ideone.com/Nd2eba
quelle
Dies kann in O (n ^ 2) durch dynamische Programmierung gelöst werden.
Verarbeiten Sie die Eingabeelemente der Reihe nach und führen Sie für jedes Element eine Liste mit Tupeln. Jedes Tupel (A, B) für das Element i bezeichnet A = Länge der am längsten ansteigenden Teilsequenz, die bei i endet, und B = Index des Vorgängers der Liste [i] in der am längsten ansteigenden Teilsequenz, die an der Liste [i endet ].
Beginnen Sie mit Element 1, die Liste der Tupel für Element 1 lautet [(1,0)] für Element i, scannen Sie die Liste 0..i und suchen Sie die Elementliste [k] so, dass Liste [k] <Liste [i] ist der Wert von A für Element i, Ai Ak + 1 und Bi ist k. Wenn es mehrere solcher Elemente gibt, fügen Sie sie der Liste der Tupel für Element i hinzu.
Suchen Sie am Ende alle Elemente mit dem Maximalwert A (Länge des LIS, das am Element endet) und verfolgen Sie die Liste mithilfe der Tupel, um die Liste abzurufen.
Ich habe den Code dafür unter http://www.edufyme.com/code/?id=66f041e16a60928b05a7e228a89c3799 geteilt
quelle
O (n ^ 2) Java-Implementierung:
quelle
Obwohl es einen Weg gibt, wie Sie dies in O (nlogn) -Zeit lösen können (dies löst sich in O (n ^ 2) -Zeit), ergibt dieser Weg dennoch den dynamischen Programmieransatz, der ebenfalls gut ist.
quelle
Hier ist meine Leetcode-Lösung mit binärer Suche: ->
quelle
Einfachste LIS-Lösung in C ++ mit O (nlog (n)) Zeitkomplexität
AUSGABE:
4
quelle
Längste zunehmende Folge (Java)
quelle
Ich habe LIS in Java mithilfe von Dynamic Programming and Memoization implementiert. Zusammen mit dem Code habe ich eine Komplexitätsberechnung durchgeführt, dh warum es O (n Log (base2) n) ist. Aus meiner Sicht sind theoretische oder logische Erklärungen gut, aber praktische Demonstrationen sind immer besser zum Verständnis.
Während ich den obigen Code ausgeführt habe -
quelle