Uwaga na temat metodologii
Pomyślałem trochę o tym problemie i doszedłem do rozwiązania. Kiedy przeczytałem odpowiedź Saeeda Amiriego , zdałem sobie sprawę, że wymyśliłem specjalną wersję standardowego najdłuższego algorytmu znajdowania podsekwencji dla sekwencji długości 3. Podaję sposób, w jaki wymyśliłem rozwiązanie, ponieważ tak myślę jest interesującym przykładem rozwiązywania problemów.
Wersja dwuelementowa
Zacznijmy od małego: zamiast szukać trzech wskaźników, w których elementy są w porządku, spójrzmy na dwa: takie, że A [ i ] < A [ j ] .i<jA[i]<A[j]
Jeśli maleje (tj. ∀ i < j , A [ i ] ≥ A [ j ] lub równoważnie ∀ i , A [ i ] ≥ A [ i + 1 ] ), to nie ma takich wskaźników. W przeciwnym razie istnieje indeks i taki, że A [ i ] < A [ i + 1 ] .A∀i<j,A[i]≥A[j]∀i,A[i]≥A[i+1]iA[i]<A[i+1]
Ta sprawa jest bardzo prosta; postaramy się to uogólnić. Pokazuje, że opisany problem nie jest do rozwiązania: wymagane wskaźniki nie zawsze istnieją. Poprosimy więc raczej, aby algorytm zwrócił prawidłowe indeksy, jeśli istnieją, lub poprawnie twierdzi, że takich indeksów nie ma.
Wymyślanie algorytmu
Użyję terminu podsekwencja, aby oznaczać wyciąg z tablicy składający się z indeksów, które mogą nie być następujące po sobie ( ( A [ i 1 ] , … , A [ i m ] ) z i 1 < ⋯ < i m ), i uruchomić oznacza kolejne elementy A ( ( A [ i ] , A [ i + 1 ] , … , AA(A[i1],…,A[im])i1<⋯<imA ).(A[i],A[i+1],…,A[i+m−1])
Właśnie widzieliśmy, że wymagane wskaźniki nie zawsze istnieją. Naszą strategią jest badanie, gdy wskaźniki nie istnieją. Zrobimy to, zakładając, że próbujemy znaleźć wskaźniki i zobaczyć, jak nasze wyszukiwanie może się nie udać. Wtedy przypadki, w których wyszukiwanie się nie powiedzie, zapewnią algorytm do znalezienia wskaźników.
Dzięki dwóm indeksom możemy znaleźć kolejne indeksy. Przy trzech indeksach możemy nie być w stanie wymyślić i k = j + 1 . Możemy jednak rozważyć przypadek rozwiązania szeregu trzech ściśle rosnących elementów ( A [ i ] < A [ i + 1 ] < A [ i + 2 ] ), ponieważ łatwo jest rozpoznać takie przebiegi, i zobacz, jak ten warunek może nie zostać spełniony. Załóżmy, że sekwencja nie ma ściśle rosnącego ciągu długości 3.j=i+1k=j+1A[i]<A[i+1]<A[i+2]
Sekwencja ma tylko ściśle rosnące przebiegi o długości 2 (które nazywam parami uporządkowanymi w skrócie), oddzielone malejącym przebiegiem długości co najmniej 2. W celu ściśle zwiększającego się przebiegu aby być częścią rosnącej sekwencji 3-elementowej, musi istnieć wcześniejszy element i taki, że A [ i ] < A [ j ] lub późniejszy element k taki, że A [ j + 1 ] < A [ kA[j]<A[j+1]iA[i]<A[j]k .A[j+1]<A[k]
Przypadek, w którym nie ma ani ani k , ma miejsce, gdy każda uporządkowana para jest całkowicie niższa niż następna. To nie wszystko: kiedy pary są przeplatane, musimy je dokładniej porównać.ik
Najbardziej wysunięty w lewo element rosnącego podsekwencji musi przyjść wcześnie i być mały. Następny element j musi być większy, ale możliwie najmniejszy, aby móc znaleźć trzeci większy element k . Pierwszy element i nie zawsze jest najmniejszym elementem w sekwencji i nie zawsze jest to pierwszy element, dla którego istnieje kolejny większy element - czasami jest dalej niższa 2-elementowa podsekwencja, a czasem jest lepsza pasuje do już znalezionego minimum.ijki
Przechodząc od lewej do prawej, wstępnie wybieramy najmniejszy element jako . Jeśli znajdziemy większy element dalej w prawo, wybieramy tę parę jako próbną ( i , j ) . Jeśli znajdziemy jeszcze większy k , wygraliśmy. Kluczową rzeczą do zapamiętania jest to, że nasz wybór i i nasz wybór ( i , j ) są aktualizowane niezależnie: jeśli mamy kandydata ( i , j ) i stwierdzimy, że i ′ > j , że A [ i ′ ] < A [i(i,j)ki(i,j)(i,j)i′>j , i ′ zostaje kolejnym kandydatem i, ale ( i , j ) pozostaje. Tylko jeśli znajdziemy j ′ takie, że A [ j ′ ] < A [ j ] stanie się ( i ′ , j ′ ) nową parą kandydatów.A[i′]<A[i]i′i(i,j)j′A[j′]<A[j](i′,j′)
Oświadczenie algorytmu
Podany w składni Pythona, ale uwaga, że go nie testowałem.
def subsequence3(A):
"""Return the indices of a subsequence of length 3, or None if there is none."""
index1 = None; value1 = None
index2 = None; value2 = None
for i in range(0,len(A)):
if index1 == None or A[i] < value1:
index1 = i; value1 = A[i]
else if A[i] == value1: pass
else if index2 == None:
index2 = (index1, i); value2 = (value1, A[i])
else if A[i] < value2[1]:
index2[1] = i; value2[1] = A[i]
else if A[i] > value2[1]:
return (index2[0], index2[1], i)
return None
Szkic próbny
index1
jest indeksem minimalnej części tablicy, która została już przemierzona (jeśli występuje kilka razy, zachowujemy pierwsze wystąpienie) lub None
przed przetworzeniem pierwszego elementu. index2
przechowuje wskaźniki rosnącej podsekwencji o długości 2 w już przemierzonej części tablicy, która ma najniższy największy element lub None
jeśli taka sekwencja nie istnieje.
Kiedy return (index2[0], index2[1], i)
uruchamia się, mamy value2[0] < value[1]
(jest to niezmiennik value2
) i value[1] < A[i]
(oczywiste z kontekstu). Jeśli pętla kończy się bez wywoływania wczesnego powrotu, value1 == None
w takim przypadku nie ma rosnącego podsekwencji o długości 2, nie mówiąc już o 3, lub value1
zawiera rosnącą podsekwencję o długości 2, która ma najniższy największy element. W tym drugim przypadku mamy ponadto niezmienność, że żadna rosnąca podsekwencja długości 3 nie kończy się wcześniej niż value1
; dlatego ostatni element każdego takiego podsekwencji, dodany do value2
, tworzy rosnący podsekwencja o długości 3: ponieważ mamy również niezmiennik, który value2
nie jest częścią rosnącego podsekwencji o długości 3 zawartej w już przemierzonej części tablicy, tam nie ma takiego podsekwencji w całej tablicy.
Udowodnienie wyżej wymienionych niezmienników pozostaje ćwiczeniem dla czytelnika.
Złożoność
O(1)O(1)O(n)
Formalny dowód
Pozostawione jako ćwiczenie dla czytelnika.