J'ai besoin de trouver un (pseudo) code efficace pour résoudre le problème suivant:
Compte tenu de deux séquences de (pas nécessairement distinctes) entiers (a[1], a[2], ..., a[n])
et (b[1], b[2], ..., b[n])
, trouver le maximum de d
telle sorte que a[n-d+1] == b[1]
, a[n-d+2] == b[2]
... et a[n] == b[d]
.
Ce ne sont pas des devoirs, j'ai trouvé cela en essayant de contracter deux tenseurs sur autant de dimensions que possible. Je soupçonne qu'un algorithme efficace existe (peut O(n)
- être ?), Mais je ne peux pas trouver quelque chose qui ne l'est pas O(n^2)
. L' O(n^2)
approche serait la boucle évidente d
puis une boucle intérieure sur les articles pour vérifier la condition requise jusqu'à atteindre le maximum d
. Mais je soupçonne que quelque chose de mieux que cela est possible.
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.a
et le début deb
. Comme ça .m
est le nombre d'éléments dansa
, etn
est le nombre d'éléments dansb
. Malheureusement, je n'ai pas suffisamment d'expérience avec KMP pour vous dire comment l'adapter.Réponses:
Vous pouvez utiliser l' algorithme z , un algorithme de temps linéaire ( O (n) ) qui:
Vous devez concaténer vos tableaux ( b + a ) et exécuter l'algorithme sur le tableau construit résultant jusqu'au premier i tel que Z [i] + i == m + n .
Par exemple, pour a = [1, 2, 3, 6, 2, 3] & b = [2, 3, 6, 2, 1, 0], la concaténation serait [2, 3, 6, 2, 1 , 0, 1, 2, 3, 6, 2, 3] ce qui donnerait Z [10] = 2 satisfaisant Z [i] + i = 12 = m + n .
la source
Pour la complexité temps / espace O (n), l'astuce consiste à évaluer les hachages pour chaque sous-séquence. Considérez le tableau
b
:En utilisant la méthode de Horner , vous pouvez évaluer tous les hachages possibles pour chaque sous-séquence. Choisissez une valeur de base
B
(supérieure à toute valeur dans vos deux tableaux):Notez que vous pouvez évaluer chaque séquence en temps O (1), en utilisant le résultat de la séquence précédente, d'où tous les coûts de travail O (n).
Vous avez maintenant un tableau
Hb = [h(b1), h(b2), ... , h(bn)]
, oùHb[i]
est le hachage deb1
jusqu'àbi
.Faites la même chose pour le tableau
a
, mais avec une petite astuce:Vous devez noter que lorsque vous passez d'une séquence à une autre, vous multipliez toute la séquence précédente par B et ajoutez la nouvelle valeur multipliée par B. Par exemple:
Vous avez maintenant un tableau
Ha = [h(an), h(an-1), ... , h(a1)]
, oùHa[i]
est le hachage deai
jusqu'àan
.Maintenant, vous pouvez comparer
Ha[d] == Hb[d]
toutes lesd
valeurs de n à 1, si elles correspondent, vous avez votre réponse.Cela signifie que deux séquences différentes peuvent avoir le même hachage, mais deux séquences égales auront toujours le même hachage.
la source
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):
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:
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 lewhile
corps et ne peut pas devenir négative. Cela ne peut se produire que si ak++
é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 duwhile
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:
Afficher l'extrait de code
la source