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):
index: 0 1 2 3 4 5 6 7 8
b: a c a a c a a c d
ref: 0 0 0 1 0 0 1 0 5
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:
function overlapCount(a, b) {
// Deal with cases where the strings differ in length
let startA = 0;
if (a.length > b.length) startA = a.length - b.length;
let endB = b.length;
if (a.length < b.length) endB = a.length;
// Create a back-reference for each index
// that should be followed in case of a mismatch.
// We only need B to make these references:
let map = Array(endB);
let k = 0; // Index that lags behind j
map[0] = 0;
for (let j = 1; j < endB; j++) {
if (b[j] == b[k]) {
map[j] = map[k]; // skip over the same character (optional optimisation)
} else {
map[j] = k;
}
while (k > 0 && b[j] != b[k]) k = map[k];
if (b[j] == b[k]) k++;
}
// Phase 2: use these references while iterating over A
k = 0;
for (let i = startA; i < a.length; i++) {
while (k > 0 && a[i] != b[k]) k = map[k];
if (a[i] == b[k]) k++;
}
return k;
}
console.log(overlapCount("ababaaaabaabab", "abaababaaz")); // 7
Obwohl es verschachtelte while
Schleifen gibt, haben diese insgesamt nicht mehr Iterationen als n . Dies liegt daran, dass der Wert von k im while
Körper streng abnimmt und nicht negativ werden kann. Dies kann nur geschehen, wenn dies so k++
oft ausgeführt wurde, dass genügend Platz für solche Abnahmen vorhanden ist. Alles in allem kann es also nicht mehr Hinrichtungen des while
Körpers geben als k++
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:
function overlapCount(a, b) {
// Deal with cases where the strings differ in length
let startA = 0;
if (a.length > b.length) startA = a.length - b.length;
let endB = b.length;
if (a.length < b.length) endB = a.length;
// Create a back-reference for each index
// that should be followed in case of a mismatch.
// We only need B to make these references:
let map = Array(endB);
let k = 0; // Index that lags behind j
map[0] = 0;
for (let j = 1; j < endB; j++) {
if (b[j] == b[k]) {
map[j] = map[k]; // skip over the same character (optional optimisation)
} else {
map[j] = k;
}
while (k > 0 && b[j] != b[k]) k = map[k];
if (b[j] == b[k]) k++;
}
// Phase 2: use these references while iterating over A
k = 0;
for (let i = startA; i < a.length; i++) {
while (k > 0 && a[i] != b[k]) k = map[k];
if (a[i] == b[k]) k++;
}
return k;
}
// I/O handling
let [inputA, inputB] = document.querySelectorAll("input");
let output = document.querySelector("pre");
function refresh() {
let a = inputA.value;
let b = inputB.value;
let count = overlapCount(a, b);
let padding = a.length - count;
// Apply some HTML formatting to highlight the overlap:
if (count) {
a = a.slice(0, -count) + "<b>" + a.slice(-count) + "</b>";
b = "<b>" + b.slice(0, count) + "</b>" + b.slice(count);
}
output.innerHTML = count + " overlapping characters:\n" +
a + "\n" +
" ".repeat(padding) + b;
}
document.addEventListener("input", refresh);
refresh();
body { font-family: monospace }
b { background:yellow }
input { width: 90% }
a: <input value="acacaacaa"><br>
b: <input value="acaacaacd"><br>
<pre></pre>
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.