Preciso encontrar um código (pseudo) eficiente para resolver o seguinte problema:
Dadas duas seqüências de números inteiros (não necessariamente distintas) (a[1], a[2], ..., a[n])
e (b[1], b[2], ..., b[n])
, encontrar a máxima d
tal que a[n-d+1] == b[1]
, a[n-d+2] == b[2]
, ..., e a[n] == b[d]
.
Isso não é tarefa de casa, na verdade, eu inventei isso ao tentar contratar dois tensores ao longo de tantas dimensões quanto possível. Suspeito que exista um algoritmo eficiente (talvez O(n)
?), Mas não consigo criar algo que não seja O(n^2)
. A O(n^2)
abordagem seria o loop óbvio ativado d
e, em seguida, um loop interno nos itens para verificar a condição necessária até atingir o máximo d
. Mas suspeito que algo melhor que isso seja possível.
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.a
e o início deb
. Assim .m
é o número de elementos ema
en
é o número de elementos emb
. Infelizmente, não tenho experiência suficiente com o KMP para dizer como adaptá-lo.Respostas:
Você pode utilizar o algoritmo z , um algoritmo de tempo linear ( O (n) ) que:
Você precisa concatenar suas matrizes ( b + a ) e executar o algoritmo na matriz construída resultante até o primeiro i, de modo que Z [i] + i == m + n .
Por exemplo, para a = [1, 2, 3, 6, 2, 3] & b = [2, 3, 6, 2, 1, 0], a concatenação seria [2, 3, 6, 2, 1 , 0, 1, 2, 3, 6, 2, 3] o que daria Z [10] = 2 cumprindo Z [i] + i = 12 = m + n .
fonte
Para O (n) complexidade de tempo / espaço, o truque é avaliar os hashes para cada subsequência. Considere a matriz
b
:Usando o método de Horner , você pode avaliar todos os hashes possíveis para cada subsequência. Escolha um valor base
B
(maior que qualquer valor nas duas matrizes):Observe que você pode avaliar cada sequência no tempo O (1), usando o resultado da sequência anterior, portanto, todos os custos do trabalho O (n).
Agora você tem uma matriz
Hb = [h(b1), h(b2), ... , h(bn)]
, ondeHb[i]
está o hash deb1
atébi
.Faça o mesmo para a matriz
a
, mas com um pequeno truque:Você deve observar que, ao passar de uma sequência para outra, multiplica toda a sequência anterior por B e adiciona o novo valor multiplicado por B. Por exemplo:
Agora você tem uma matriz
Ha = [h(an), h(an-1), ... , h(a1)]
, ondeHa[i]
está o hash deai
atéan
.Agora, você pode comparar
Ha[d] == Hb[d]
para todos osd
valores de n a 1, se eles corresponderem, você tem sua resposta.Isso significa que duas seqüências diferentes podem ter o mesmo hash, mas duas seqüências iguais sempre terão o mesmo hash.
fonte
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):
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:
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 nowhile
corpo e não pode se tornar negativo. Isso só pode acontecer quandok++
foi executado tantas vezes para dar espaço suficiente para essas diminuições. Portanto, em suma, não pode haver mais execuções dowhile
corpo do quek++
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:
Mostrar snippet de código
fonte