Necesito encontrar un código eficiente (pseudo) para resolver el siguiente problema:
Dadas dos secuencias de números enteros (no necesariamente distintos) (a[1], a[2], ..., a[n])
y (b[1], b[2], ..., b[n])
, encontrar el máximo d
tal que a[n-d+1] == b[1]
, a[n-d+2] == b[2]
, ..., y a[n] == b[d]
.
Esto no es tarea, en realidad se me ocurrió cuando intenté contraer dos tensores a lo largo de todas las dimensiones posibles. Sospecho que existe un algoritmo eficiente ( O(n)
¿ tal vez ?), Pero no puedo encontrar algo que no lo sea O(n^2)
. El O(n^2)
enfoque sería el bucle obvio d
y luego un bucle interno en los elementos para verificar la condición requerida hasta alcanzar el máximo d
. Pero sospecho que algo mejor que esto es posible.
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.a
con el comienzo deb
. Al igual que este .m
es el número de elementos ena
, yn
es el número de elementos enb
. Desafortunadamente, no tengo suficiente experiencia con KMP para decirle cómo adaptarlo.Respuestas:
Puede utilizar el algoritmo z , un algoritmo de tiempo lineal ( O (n) ) que:
Debe concatenar sus matrices ( b + a ) y ejecutar el algoritmo en la matriz construida resultante hasta el primer i tal que Z [i] + i == m + n .
Por ejemplo, para a = [1, 2, 3, 6, 2, 3] & b = [2, 3, 6, 2, 1, 0], la concatenación sería [2, 3, 6, 2, 1 , 0, 1, 2, 3, 6, 2, 3] que produciría Z [10] = 2 cumpliendo Z [i] + i = 12 = m + n .
fuente
Para O (n) complejidad tiempo / espacio, el truco es evaluar los hashes para cada subsecuencia. Considere la matriz
b
:Usando el método de Horner , puede evaluar todos los hashes posibles para cada subsecuencia. Elija un valor base
B
(mayor que cualquier valor en ambas matrices):Tenga en cuenta que puede evaluar cada secuencia en el tiempo O (1), utilizando el resultado de la secuencia anterior, por lo tanto, todo el trabajo cuesta O (n).
Ahora tiene una matriz
Hb = [h(b1), h(b2), ... , h(bn)]
, de dóndeHb[i]
es el hash desdeb1
hastabi
.Haga lo mismo para la matriz
a
, pero con un pequeño truco:Debe tener en cuenta que, cuando pasa de una secuencia a otra, multiplica toda la secuencia anterior por B y agrega el nuevo valor multiplicado por B. Por ejemplo:
Ahora tiene una matriz
Ha = [h(an), h(an-1), ... , h(a1)]
, de dóndeHa[i]
es el hash desdeai
hastaan
.Ahora, puede comparar
Ha[d] == Hb[d]
todos losd
valores de n a 1, si coinciden, tiene su respuesta.Esto significa que dos secuencias diferentes pueden tener el mismo hash, pero dos secuencias iguales siempre tendrán el mismo hash.
fuente
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):
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:
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 elwhile
cuerpo y no puede volverse negativo. Esto solo puede suceder cuandok++
se ejecutó tantas veces para dar suficiente espacio para tales disminuciones. Así que, en general, no puede haber más ejecuciones delwhile
cuerpo quek++
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:
Mostrar fragmento de código
fuente