Cela peut en effet se faire en temps linéaire, O (n) et O (n) espace supplémentaire. Je suppose que les tableaux d'entrée sont des chaînes de caractères, mais ce n'est pas essentiel.
Une méthode naïve rechercherait - après avoir fait correspondre k caractères égaux - un caractère qui ne correspond pas, et reculerait de k-1 unités dans a , réinitialiser l'index en b , puis démarrer le processus de correspondance à partir de là. Cela représente clairement le pire des cas O (n²) .
Pour éviter ce processus de retour en arrière, nous pouvons observer que revenir en arrière n'est pas utile si nous n'avons pas rencontré le caractère b [0] lors du balayage des k-1 derniers caractères. Si nous avons fait constater que le caractère, retours en arrière alors à cette position ne serait utile, si ce k sized substring nous avons eu une répétition périodique.
Par exemple, si nous regardons la sous-chaîne "abcabc" quelque part dans a , et b est "abcabd", et que nous constatons que le caractère final de b ne correspond pas, nous devons considérer qu'une correspondance réussie peut commencer au deuxième "a" dans la sous-chaîne, et nous devons déplacer notre indice actuel en b en conséquence avant de poursuivre la comparaison.
L'idée est alors de faire un prétraitement basé sur la chaîne b pour enregistrer les références arrières dans b qui sont utiles pour vérifier quand il y a un décalage. Ainsi, par exemple, si b est "acaacaacd", nous pourrions identifier ces références arrières basées sur 0 (placées sous chaque caractère):
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
Par exemple, si nous avons un égal à "acaacaaca", le premier décalage se produit sur le caractère final. Les informations ci-dessus indiquent ensuite à l'algorithme de revenir en b à l'index 5, car "acaac" est courant. Et puis, en ne changeant que l'indice actuel dans b, nous pouvons continuer l'appariement à l'indice actuel de a . Dans cet exemple, la correspondance du dernier caractère réussit.
Avec cela, nous pouvons optimiser la recherche et nous assurer que l'index dans un peut toujours avancer.
Voici une implémentation de cette idée en JavaScript, en utilisant uniquement la syntaxe la plus basique de ce langage:
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
Bien qu'il existe des while
boucles imbriquées , celles-ci n'ont pas plus d'itérations au total que n . En effet, la valeur de k diminue strictement dans le while
corps et ne peut pas devenir négative. Cela ne peut se produire que si a k++
été exécuté autant de fois pour donner suffisamment de place à de telles diminutions. Donc, dans l'ensemble, il ne peut y avoir plus d'exécutions du while
corps qu'il n'y a d' k++
exécutions, et ce dernier est clairement O (n).
Pour terminer, vous pouvez trouver ici le même code que ci-dessus, mais dans un extrait interactif: vous pouvez saisir vos propres chaînes et voir le résultat de manière interactive:
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]
, puis accédez au tableau dea
hachage de calcul poura[1] to a[d]
si cela correspond, c'est votre réponse, sinon calculez le hachage poura[2] to a[d+1]
en réutilisant le hachage calculé poura[1] to a[d]
. Mais je ne sais pas si les objets du tableau peuvent être calculés sur un hachage roulant.