Questo può effettivamente essere fatto in tempo lineare, O (n) e O (n) spazio extra. Presumo che le matrici di input siano stringhe di caratteri, ma questo non è essenziale.
Un metodo ingenuo dovrebbe - dopo aver trovato k caratteri uguali - trovare un carattere che non corrisponde, e tornare indietro di k-1 unità in a , resettare l'indice in b e quindi iniziare il processo di corrispondenza da lì. Ciò rappresenta chiaramente un caso peggiore O (n²) .
Per evitare questo processo di backtracking, possiamo osservare che tornare indietro non è utile se non abbiamo riscontrato il carattere b [0] durante la scansione degli ultimi caratteri k-1 . Se abbiamo fatto scoprire che il carattere, poi marcia indietro a quella posizione sarebbe solo utile, se in quel k size substring abbiamo avuto una ripetizione periodica.
Ad esempio, se osserviamo la sottostringa "abcabc" da qualche parte in a , e b è "abcabd", e troviamo che il carattere finale di b non corrisponde, dobbiamo considerare che una corrispondenza corretta potrebbe iniziare alla seconda "a" nella sottostringa e dovremmo spostare di conseguenza il nostro indice corrente in b prima di continuare il confronto.
L'idea è quindi di eseguire una pre-elaborazione basata sulla stringa b per registrare i riferimenti a ritroso in b che sono utili per verificare in caso di mancata corrispondenza. Quindi, ad esempio, se b è "acaacaacd", potremmo identificare questi riferimenti indietro basati su 0 (posti sotto ogni carattere):
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
Ad esempio, se abbiamo un "acaacaaca" uguale, la prima discrepanza si verifica sul personaggio finale. Le informazioni di cui sopra dicono quindi all'algoritmo di tornare in b all'indice 5, poiché "acaac" è comune. E quindi con la sola modifica dell'indice corrente in b possiamo continuare la corrispondenza con l'indice corrente di a . In questo esempio, la corrispondenza del personaggio finale ha esito positivo.
Con questo siamo in grado di ottimizzare la ricerca e fare in modo che l'indice in un può sempre progredire in avanti.
Ecco un'implementazione di quell'idea in JavaScript, usando solo la sintassi più semplice di quella lingua:
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
Sebbene ci siano while
loop nidificati , questi non hanno più iterazioni in totale di n . Questo perché il valore di k diminuisce rigorosamente nel while
corpo e non può diventare negativo. Questo può accadere solo quando è k++
stato eseguito più volte per dare abbastanza spazio per tali riduzioni. Quindi, tutto sommato, non ci possono essere più esecuzioni del while
corpo di quante ci siano k++
esecuzioni, e quest'ultima è chiaramente O (n).
Per completare, qui puoi trovare lo stesso codice sopra, ma in uno snippet interattivo: puoi inserire le tue stringhe e vedere il risultato in modo interattivo:
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]
e poi vai all'arraya
calcola l'hash pera[1] to a[d]
se corrisponde, allora questa è la tua risposta, se non calcola l'hash pera[2] to a[d+1]
riutilizzando l'hash calcolato pera[1] to a[d]
. Ma non so se gli oggetti nell'array sono suscettibili di calcolarli su un hash rolling.