Chcę udowodnić lub obalić istnienie algorytmu, który, biorąc pod uwagę tablicę liczb całkowitych, znajduje trzy indeksy i takie, że i (lub stwierdza, że nie ma takiej potrójnej) w czasie liniowym.
To nie jest zadanie domowe; Widziałem to na forum programistycznym sformułowanym jako „spróbuj wdrożyć taki algorytm”. Podejrzewam, że jest to niemożliwe po różnych eksperymentach. Moja intuicja mi to mówi, ale tak naprawdę to na nic się nie liczy.
Chciałbym to udowodnić formalnie. Jak ty to robisz? Idealnie chciałbym zobaczyć dowód ułożony krok po kroku, a następnie, jeśli masz taką skłonność, wyjaśnienie, w jaki sposób można ogólnie udowodnić / obalić takie proste pytania. Jeśli to pomaga, kilka przykładów:
[1,5,2,0,3] → (1,2,3)
[5,6,1,2,3] → (1,2,3)
[1,5,2,3] → (1,2,3)
[5,6,1,2,7] → (1,2,7)
[5,6,1,2,7,8] → (1,2,7)
[1,2,999,3] → (1,2,999)
[999,1,2,3] → (1,2,3)
[11,12,8,9,5,6,3,4,1,2,3] → (1,2,3)
[1,5,2,0,-5,-2,-1] → (-5,-2,-1)
Przypuszczałem, że można iterować po i za każdym razem, gdy występuje i < j (czyli nasze obecne j ), tworzymy nowy potrójny i wypychamy go na tablicę. Kontynuujemy kroczenie i porównywanie każdego potrójnego, dopóki jedna z naszych potrójnych nie zostanie ukończona. Więc jak to jest , ! Ale myślę, że jest to bardziej złożone niż zwykłe O ( n ), ponieważ liczba potrójnych w naszej potrójnej tablicy odpowiada w najgorszym przypadku wielkości listy wejściowej.[1,5,2,0,-5,-2,-1] → 1..2.. -5.. -2.. -1
[1,5,2,0,-5,-2,3,-1] → 1..2.. -5.. -2.. 3
źródło
Odpowiedzi:
Jest to wariant problemu Najdłużej rosnącego podsekwencji ; jest to rozwiązanie przedstawione na Wikipedii za pomocą dwóch pomocniczych tablic i P :M P
Algorytm działa w najgorszym przypadku . Twój problem jest szczególnym przypadkiem, który pozwala powrócić, gdy L = 3, które popycha środowisko wykonawcze w dół do O ( n ), ponieważ wyszukiwanie binarne działa tylko na tablicach o długości co najwyżej dwóch, czyli w czasie O ( 1 ) w przeciwieństwie do Θ ( log n ) w ogólnym przypadku.Θ ( n logn ) L = 3 O ( n ) O ( 1 ) Θ ( logn )
Rozważ zmodyfikowany pseudo kod:
źródło
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<j A[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] i A[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<⋯<im A ).(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+1 k=j+1 A[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] i A[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ć.i k
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.i j k i
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) k i (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.
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) lubNone
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 lubNone
jeśli taka sekwencja nie istnieje.Kiedy
return (index2[0], index2[1], i)
uruchamia się, mamyvalue2[0] < value[1]
(jest to niezmiennikvalue2
) ivalue[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, lubvalue1
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 dovalue2
, tworzy rosnący podsekwencja o długości 3: ponieważ mamy również niezmiennik, któryvalue2
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ść
Formalny dowód
Pozostawione jako ćwiczenie dla czytelnika.
źródło
Najpierw przejdź przez tablicę od lewej do prawej, utrzymując stos i tablicę pomocniczą, która informuje dla każdego elementu o indeksie elementu większego od niego i na prawo od niego.
Za każdym razem, gdy rozważasz nowy element w tablicy, jeśli ten element jest większy niż górny element stosu, zrywasz go ze stosu i ustawiasz element tablicy pomocniczej odpowiadający górze, aby indeks nowego elementu był poniżej wynagrodzenie.
Kontynuuj usuwanie elementów ze stosu i ustawianie odpowiedniego indeksu, podczas gdy bieżący element jest większy. Gdy góra ma element, który nie jest mniejszy (lub staje się pusty), wepchnij bieżący element na stos i przejdź do następnego elementu tablicy, powtarzając powyższy krok.
Wykonaj kolejne przejście (i kolejną tablicę Aux), ale idąc od prawej do lewej.
Pseudo kod dla pierwszego przejścia może wyglądać następująco:
źródło