De hecho, esto se puede hacer en tiempo lineal, O (n) y O (n) espacio extra. Asumiré que las matrices de entrada son cadenas de caracteres, pero esto no es esencial.
Un método ingenuo podría, después de hacer coincidir k caracteres que son iguales, encontrar un carácter que no coincida y retroceder k-1 unidades en a , restablecer el índice en b , y luego comenzar el proceso de coincidencia desde allí. Esto representa claramente el peor de los casos O (n²) .
Para evitar este proceso de retroceso, podemos observar que retroceder no es útil si no hemos encontrado el carácter b [0] al escanear los últimos caracteres k-1 . Si nos hicimos encontrar que carácter, a continuación, dar marcha atrás a esa posición sólo sería útil, si en ese k sized subcadena tuvimos una repetición periódica.
Por ejemplo, si observamos la subcadena "abcabc" en algún lugar de a , y b es "abcabd", y encontramos que el carácter final de b no coincide, debemos considerar que una coincidencia exitosa podría comenzar en la segunda "a" en la subcadena, y debemos mover nuestro índice actual en b de nuevo en consecuencia antes de continuar la comparación.
La idea es hacer un preprocesamiento basado en la cadena b para registrar las referencias en b que son útiles para verificar si hay una falta de coincidencia. Entonces, por ejemplo, si b es "acaacaacd", podríamos identificar estas referencias inversas basadas en 0 (debajo de cada carácter):
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
Por ejemplo, si tenemos un igual a "acaacaaca", el primer desajuste ocurre en el personaje final. La información anterior le dice al algoritmo que regrese en b al índice 5, ya que "acaac" es común. Y luego, con solo cambiar el índice actual en b , podemos continuar la coincidencia en el índice actual de a . En este ejemplo, la coincidencia del personaje final tiene éxito.
Con esto podemos optimizar la búsqueda y asegúrese de que el índice en un siempre puede progresar hacia delante.
Aquí hay una implementación de esa idea en JavaScript, utilizando solo la sintaxis más básica de ese lenguaje:
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
Aunque hay while
bucles anidados , estos no tienen más iteraciones en total que n . Esto se debe a que el valor de k disminuye estrictamente en el while
cuerpo y no puede volverse negativo. Esto solo puede suceder cuando k++
se ejecutó tantas veces para dar suficiente espacio para tales disminuciones. Así que, en general, no puede haber más ejecuciones del while
cuerpo que k++
ejecuciones, y esta última es claramente O (n).
Para completar, aquí puede encontrar el mismo código que el anterior, pero en un fragmento interactivo: puede ingresar sus propias cadenas y ver el resultado de manera interactiva:
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]
y luego vaya a la matriz paraa
calcular el hasha[1] to a[d]
si eso coincide, entonces esa es su respuesta, si no, calcule el hasha[2] to a[d+1]
reutilizando el hash calculado paraa[1] to a[d]
. Pero no sé si los objetos en la matriz son susceptibles de que se calcule un hash rodante.