Muszę znaleźć wydajny (pseudo) kod, aby rozwiązać następujący problem:
Biorąc pod uwagę dwie sekwencje (niekoniecznie różnych) liczb całkowitych (a[1], a[2], ..., a[n])
i (b[1], b[2], ..., b[n])
znajdź maksymalna d
takie, że a[n-d+1] == b[1]
, a[n-d+2] == b[2]
, ..., i a[n] == b[d]
.
To nie jest praca domowa, właściwie to wymyśliłem, próbując skurczyć dwa tensory wzdłuż możliwie największej liczby wymiarów. Podejrzewam, że istnieje skuteczny algorytm (może O(n)
?), Ale nie mogę wymyślić czegoś, co nie jest O(n^2)
. O(n^2)
Podejście byłoby oczywistym pętla na d
czym wewnętrzna pętla na pozycje do sprawdzenia stanu wymaganego momentu trafienia maksimum d
. Ale podejrzewam, że możliwe jest coś lepszego niż to.
b[1] to b[d]
a następnie przejdź do tablicya
hash obliczenia,a[1] to a[d]
jeśli to pasuje, to twoja odpowiedź, jeśli nie, oblicz hasha[2] to a[d+1]
przez ponowne użycie obliczonego skrótua[1] to a[d]
. Ale nie wiem, czy obiekty w tablicy są podatne na obliczanie na nich zmiennego kroczącego.a
a początkiemb
. Tak .m
jest liczba elementów wa
, in
jest liczba elementów wb
. Niestety nie mam wystarczającego doświadczenia z KMP, aby powiedzieć ci, jak go dostosować.Odpowiedzi:
Możesz użyć algorytmu z, algorytmu czasu liniowego ( O (n) ), który:
Musisz połączyć swoje tablice ( b + a ) i uruchomić algorytm na wynikowej skonstruowanej tablicy do pierwszego i takiego, że Z [i] + i == m + n .
Na przykład dla a = [1, 2, 3, 6, 2, 3] i b = [2, 3, 6, 2, 1, 0] konkatenacja wynosiłaby [2, 3, 6, 2, 1 , 0, 1, 2, 3, 6, 2, 3], co dałoby Z [10] = 2 spełniające Z [i] + i = 12 = m + n .
źródło
W przypadku złożoności czasowej / przestrzennej O (n) sztuczka polega na ocenie skrótów dla każdej podsekwencji. Rozważ tablicę
b
:Za pomocą metody Hornera możesz ocenić wszystkie możliwe wartości skrótu dla każdej podsekwencji. Wybierz wartość podstawową
B
(większą niż dowolna wartość w obu twoich tablicach):Zauważ, że możesz ocenić każdą sekwencję w czasie O (1), korzystając z wyniku poprzedniej sekwencji, stąd wszystkie koszty pracy O (n).
Teraz masz tablicę
Hb = [h(b1), h(b2), ... , h(bn)]
, gdzieHb[i]
jest skrót odb1
dobi
.Zrób to samo dla tablicy
a
, ale z małą sztuczką:Należy pamiętać, że przechodząc z jednej sekwencji do drugiej, mnożymy całą poprzednią sekwencję przez B i dodajemy nową wartość pomnożoną przez B. Na przykład:
Teraz masz tablicę
Ha = [h(an), h(an-1), ... , h(a1)]
, gdzieHa[i]
jest skrót odai
doan
.Teraz możesz porównać
Ha[d] == Hb[d]
dla wszystkichd
wartości od n do 1, jeśli pasują, masz odpowiedź.Oznacza to, że dwie różne sekwencje mogą mieć ten sam skrót, ale dwie równe sekwencje zawsze będą miały ten sam skrót.
źródło
Można to rzeczywiście zrobić w czasie liniowym, O (n) i O (n) dodatkowej przestrzeni. Zakładam, że tablice wejściowe są ciągami znaków, ale nie jest to konieczne.
Naiwna metoda - po dopasowaniu k równych znaków - znalazłaby znak, który nie pasuje, i cofnąłaby jednostki k-1 w a , zresetowała indeks wb , a następnie rozpoczęła proces dopasowania. To wyraźnie reprezentuje najgorszy przypadek O (n²) .
Aby uniknąć tego procesu powrotu, możemy zauważyć, że cofnięcie się nie jest przydatne, jeśli nie napotkaliśmy znaku b [0] podczas skanowania ostatnich znaków k-1 . Gdybyśmy zrobili znaleźć ten znak, a następnie wycofywania do tej pozycji byłby tylko przydatny, jeśli w to k sized podciąg mieliśmy okresowe powtarzanie.
Na przykład, jeśli spojrzymy na podciąg „abcabc” gdzieś w a , a b to „abcabd” i stwierdzimy, że końcowy znak b nie pasuje, musimy wziąć pod uwagę, że pomyślne dopasowanie może rozpocząć się od drugiego „a” w podciągu i powinniśmy odpowiednio przenieść nasz bieżący indeks wb przed kontynuowaniem porównania.
Pomysł polega na tym, aby wykonać wstępne przetwarzanie w oparciu o ciąg b, aby zalogować wstecz referencje w b, które są przydatne do sprawdzenia, kiedy występuje niezgodność. Na przykład, jeśli b jest „acaacaacd”, moglibyśmy zidentyfikować te odwołania wsteczne oparte na 0 (umieść poniżej każdego znaku):
Na przykład, jeśli mamy wartość równą „acaacaaca”, pierwsze niedopasowanie nastąpi na końcowej postaci. Powyższe informacje każą algorytmowi wrócić do b do indeksu 5, ponieważ „acaac” jest powszechny. A następnie zmieniając tylko bieżący indeks wb , możemy kontynuować dopasowanie przy bieżącym indeksie a . W tym przykładzie dopasowanie ostatniego znaku kończy się powodzeniem.
Dzięki temu możemy zoptymalizować szukanie i upewnij się, że indeks w zawsze może przejść do przodu.
Oto implementacja tego pomysłu w JavaScript, wykorzystująca tylko najbardziej podstawową składnię tego języka:
Chociaż istnieją zagnieżdżone
while
pętle, nie mają one w sumie więcej iteracji niż n . Jest tak, ponieważ wartość k ściśle zmniejsza się wwhile
ciele i nie może stać się ujemna. Może się to zdarzyć tylko wtedy, gdyk++
wykonano go tyle razy, aby dać wystarczająco dużo miejsca na takie obniżki. Podsumowując, nie może być więcej egzekucjiwhile
ciała niżk++
egzekucje, a ta ostatnia jest wyraźnie O (n).Aby ukończyć, możesz znaleźć ten sam kod, co powyżej, ale w interaktywnym fragmencie: możesz wprowadzić własne ciągi znaków i interaktywnie zobaczyć wynik:
Pokaż fragment kodu
źródło