Isso pode realmente ser feito em tempo linear, O (n) e O (n) espaço extra. Vou assumir que as matrizes de entrada são cadeias de caracteres, mas isso não é essencial.
Um método ingênuo - após corresponder k caracteres iguais - encontra um caractere que não corresponde e retorna k-1 unidades em a , redefine o índice em b e inicia o processo de correspondência a partir daí. Isso representa claramente o pior caso de O (n²) .
Para evitar esse processo de retorno, podemos observar que voltar atrás não será útil se não encontrarmos o caractere b [0] durante a verificação dos últimos caracteres k-1 . Se nós fez encontrar esse personagem, em seguida, recuar para essa posição só seria útil, se em que k porte substring tivemos uma repetição periódica.
Por exemplo, se olharmos para substring "abcabc" em algum lugar um , e b é "abcabd", e descobrimos que o caráter final da b não corresponder, devemos considerar que um casamento bem-sucedido pode começar na segunda "a" na substring, e devemos mover nosso índice atual em b de volta adequadamente antes de continuar a comparação.
A idéia é, então, realizar um pré-processamento com base na string b para registrar as referências anteriores em b que são úteis para verificar quando há uma incompatibilidade. Por exemplo, se b for "acaacaacd", poderíamos identificar essas referências anteriores baseadas em 0 (coloque abaixo de cada caractere):
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 exemplo, se temos um igual a "acaacaaca", a primeira incompatibilidade ocorre no caractere final. As informações acima, então, informam ao algoritmo que retorne em b ao índice 5, pois "acaac" é comum. E então, apenas alterando o índice atual em b , podemos continuar a correspondência no índice atual de a . Neste exemplo, a correspondência do caractere final é bem-sucedida.
Com isso, podemos otimizar a pesquisa e garantir que o índice em um sempre possa avançar.
Aqui está uma implementação dessa ideia em JavaScript, usando apenas a sintaxe mais básica dessa linguagem:
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
Embora existam while
loops aninhados , eles não têm mais iterações no total que n . Isso ocorre porque o valor de k diminui estritamente no while
corpo e não pode se tornar negativo. Isso só pode acontecer quando k++
foi executado tantas vezes para dar espaço suficiente para essas diminuições. Portanto, em suma, não pode haver mais execuções do while
corpo do que k++
execuções, e a última é claramente O (n).
Para concluir, aqui você pode encontrar o mesmo código acima, mas em um snippet interativo: você pode inserir suas próprias strings e ver o resultado interativamente:
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, em seguida, vá para o arraya
calcular o hash paraa[1] to a[d]
se isso corresponder, então essa é a sua resposta, se não calcular o hasha[2] to a[d+1]
reutilizando o hash calculadoa[1] to a[d]
. Mas não sei se os objetos na matriz são passíveis de cálculo de um hash rotativo neles.