Ich muss einen effizienten (Pseudo-) Code finden, um das folgende Problem zu lösen:
Gegeben seien zwei Sequenzen von (nicht notwendig verschiedenen) ganzen Zahlen (a[1], a[2], ..., a[n])
und (b[1], b[2], ..., b[n])
finden Sie das Maximum , d
so dass a[n-d+1] == b[1]
, a[n-d+2] == b[2]
, ... und a[n] == b[d]
.
Dies sind keine Hausaufgaben, ich habe sie mir tatsächlich ausgedacht, als ich versucht habe, zwei Tensoren in möglichst vielen Dimensionen zusammenzuziehen. Ich vermute, dass es einen effizienten Algorithmus gibt (vielleicht O(n)
?), Aber ich kann mir nichts einfallen lassen, was nicht der Fall ist O(n^2)
. Der O(n^2)
Ansatz wäre die offensichtliche Schleife an d
und dann eine innere Schleife an den Elementen, um den erforderlichen Zustand zu überprüfen, bis das Maximum erreicht ist d
. Aber ich vermute, dass etwas Besseres möglich ist.
b[1] to b[d]
und gehen Sie dann zum Array.a
Berechnen Sie den Hash,a[1] to a[d]
wenn dies übereinstimmt. Dies ist Ihre Antwort. Wenn nicht, berechnen Sie den Hash,a[2] to a[d+1]
indem Sie den berechneten Hash wiederverwendena[1] to a[d]
. Ich weiß jedoch nicht, ob die Objekte im Array für die Berechnung eines rollierenden Hashs geeignet sind.a
mit dem Anfang von zu findenb
. Wie dies .m
die Anzahl der Elemente ina
undn
die Anzahl der Elemente in istb
. Leider habe ich nicht genügend Erfahrung mit KMP, um Ihnen zu sagen, wie Sie es anpassen können.Antworten:
Sie können den z-Algorithmus verwenden , einen linearen Zeitalgorithmus ( O (n) ), der:
Sie müssen Ihre Arrays verketten ( b + a ) und den Algorithmus auf dem resultierenden konstruierten Array bis zum ersten i ausführen, sodass Z [i] + i == m + n .
Zum Beispiel wäre für a = [1, 2, 3, 6, 2, 3] & b = [2, 3, 6, 2, 1, 0] die Verkettung [2, 3, 6, 2, 1 , 0, 1, 2, 3, 6, 2, 3], was Z [10] = 2 ergeben würde, das Z [i] + i = 12 = m + n erfüllt .
quelle
Für die O (n) Zeit / Raum-Komplexität besteht der Trick darin, Hashes für jede Teilsequenz auszuwerten. Betrachten Sie das Array
b
:Mit der Horner-Methode können Sie alle möglichen Hashes für jede Teilsequenz auswerten. Wählen Sie einen Basiswert
B
(größer als jeder Wert in beiden Arrays):Beachten Sie, dass Sie jede Sequenz in O (1) -Zeit anhand des Ergebnisses der vorherigen Sequenz auswerten können, daher alle Auftragskosten O (n).
Jetzt haben Sie ein Array
Hb = [h(b1), h(b2), ... , h(bn)]
, woHb[i]
ist der Hash vonb1
bisbi
.Machen Sie dasselbe für das Array
a
, aber mit einem kleinen Trick:Sie müssen beachten, dass Sie beim Übergang von einer Sequenz zur nächsten die gesamte vorherige Sequenz mit B multiplizieren und den neuen Wert multipliziert mit B addieren. Beispiel:
Jetzt haben Sie ein Array
Ha = [h(an), h(an-1), ... , h(a1)]
, woHa[i]
ist der Hash vonai
bisan
.Jetzt können Sie
Ha[d] == Hb[d]
für alled
Werte von n bis 1 vergleichen. Wenn sie übereinstimmen, haben Sie Ihre Antwort.Dies bedeutet, dass zwei verschiedene Sequenzen möglicherweise denselben Hash haben, aber zwei gleiche Sequenzen immer denselben Hash haben.
quelle
Dies kann in der Tat in linearer Zeit, O (n) und O (n) zusätzlichem Raum erfolgen. Ich gehe davon aus, dass die Eingabearrays Zeichenfolgen sind, dies ist jedoch nicht unbedingt erforderlich.
Eine naive Methode würde - nach dem Abgleichen von k Zeichen, die gleich sind - ein Zeichen finden, das nicht übereinstimmt, und k-1 Einheiten in a zurückgehen, den Index in b zurücksetzen und dann den Abgleichprozess von dort aus starten. Dies ist eindeutig ein O (n²) Worst Case.
Um diesen Rückverfolgungsprozess zu vermeiden, können wir feststellen, dass ein Zurückgehen nicht sinnvoll ist, wenn wir beim Scannen der letzten k-1- Zeichen nicht auf das Zeichen b [0] gestoßen sind . Wenn wir haben diesen Charakter finden, dann zu dieser Position Rückzieher wäre nur dann sinnvoll sein, wenn in dem k sized Teilzeichen wir eine periodische Wiederholung haben.
Wenn wir zum Beispiel die Teilzeichenfolge "abcabc" irgendwo in a betrachten und b "abcabd" ist und wir feststellen, dass das endgültige Zeichen von b nicht übereinstimmt, müssen wir berücksichtigen, dass eine erfolgreiche Übereinstimmung möglicherweise beim zweiten "a" beginnt. in der Teilzeichenfolge, und wir sollten unseren aktuellen Index in b entsprechend zurück verschieben, bevor wir den Vergleich fortsetzen.
Die Idee ist dann, eine Vorverarbeitung basierend auf der Zeichenfolge b durchzuführen, um Rückverweise in b zu protokollieren , die nützlich sind, um zu überprüfen, ob eine Nichtübereinstimmung vorliegt. Wenn b beispielsweise "acaacaacd" ist, können wir diese 0-basierten Rückreferenzen identifizieren (unter jedem Zeichen):
Zum Beispiel, wenn wir einen gleich „acaacaaca“ der erste Mismatch geschieht auf dem letzten Zeichen. Die obigen Informationen weisen den Algorithmus dann an, in b zu Index 5 zurückzukehren, da "acaac" üblich ist. Und wenn wir nur den aktuellen Index in b ändern, können wir den Abgleich mit dem aktuellen Index von a fortsetzen . In diesem Beispiel ist die Übereinstimmung des letzten Zeichens dann erfolgreich.
Damit können wir die Suche optimieren und sicherstellen, dass der Index in a immer vorwärts gehen kann.
Hier ist eine Implementierung dieser Idee in JavaScript, wobei nur die grundlegendste Syntax dieser Sprache verwendet wird:
Obwohl es verschachtelte
while
Schleifen gibt, haben diese insgesamt nicht mehr Iterationen als n . Dies liegt daran, dass der Wert von k imwhile
Körper streng abnimmt und nicht negativ werden kann. Dies kann nur geschehen, wenn dies sok++
oft ausgeführt wurde, dass genügend Platz für solche Abnahmen vorhanden ist. Alles in allem kann es also nicht mehr Hinrichtungen deswhile
Körpers geben alsk++
Hinrichtungen, und letztere ist eindeutig O (n).Zum Abschluss finden Sie hier den gleichen Code wie oben, jedoch in einem interaktiven Snippet: Sie können Ihre eigenen Zeichenfolgen eingeben und das Ergebnis interaktiv anzeigen:
Code-Snippet anzeigen
quelle