Miałem to pytanie w teście Algorytmów wczoraj i nie mogę znaleźć odpowiedzi. Doprowadza mnie to do szału, bo było warte około 40 punktów. Wydaje mi się, że większość zajęć nie rozwiązała go poprawnie, ponieważ nie wymyśliłem rozwiązania w ciągu ostatnich 24 godzin.
Mając dowolny ciąg binarny o długości n, znajdź trzy równomiernie rozmieszczone w ciągu ciągu, jeśli istnieją. Napisz algorytm, który rozwiąże to w czasie O (n * log (n)).
Tak więc ciągi takie jak te mają trzy ciągi, które są „w równych odstępach”: 11100000, 0100100100
edycja: Jest to liczba losowa, więc powinna być w stanie pracować dla dowolnej liczby. Przykłady, które podałem, miały zilustrować właściwość „równomiernie rozmieszczonych”. Więc 1001011 to poprawna liczba. Z 1, 4 i 7 to te, które są rozmieszczone w równych odstępach.
Odpowiedzi:
Wreszcie! Idąc za tropami w odpowiedzi sdcvvc , mamy to: algorytm O (n log n) dla problemu! Jest to również proste, gdy to zrozumiesz. Rację mieli ci, którzy odgadli FFT.
Problem: otrzymujemy ciąg binarny
S
o długości n i chcemy znaleźć w nim trzy równo rozmieszczone jedynki. Na przykładS
może być110110010
, gdzie n = 9. Ma równomiernie rozmieszczone jedynki w pozycjach 2, 5 i 8.Zeskanuj od
S
lewej do prawej i zrób listęL
pozycji o wartości 1. WS=110110010
powyższym przykładzie mamy listę L = [1, 2, 4, 5, 8]. Ten krok to O (n). Problemem jest znalezienie arytmetyczny długości 3 wL
, to znaczy wyraźne znalezienie A, B i C, wL
taki że ba = CB lub równoważnie a + c = 2b . W powyższym przykładzie chcemy znaleźć progresję (2, 5, 8).Utwórz wielomian
p
z wyrazami x k dla każdego k inL
. W powyższym przykładzie tworzymy wielomian p (x) = (x + x 2 + x 4 + x 5 + x 8 ) . Ten krok to O (n).Znajdź wielomian
q
= p 2 , używając szybkiej transformaty Fouriera . W powyższym przykładzie otrzymujemy wielomian q (x) = x 16 + 2x 13 + 2x 12 + 3x 10 + 4x 9 + x 8 + 2x 7 + 4x 6 + 2x 5 + x 4 + 2x 3 + x 2 . Ten krok to O (n log n).Zignoruj wszystkie terminy z wyjątkiem tych odpowiadających x 2k dla niektórych k in
L
. W powyższym przykładzie otrzymujemy wyrazy x 16 , 3x 10 , x 8 , x 4 , x 2 . Ten krok jest O (n), jeśli w ogóle zdecydujesz się to zrobić.Oto kluczowy punkt: współczynnik dowolnego x 2b dla b in
L
jest dokładnie liczba par (a, c) naL
tak że a + c = 2b . [CLRS, przykł. 30.1-7] Jedna taka para to (b, b) zawsze (więc współczynnik wynosi co najmniej 1), ale jeśli istnieje jakaś inna para (a, c) , to współczynnik wynosi co najmniej 3, z (a, c ) i (c, a) . W powyższym przykładzie współczynnik x 10 wynosi 3 dokładnie ze względu na AP (2,5,8). (Współczynniki te x 2bz powyższych powodów zawsze będą liczbami nieparzystymi. A wszystkie inne współczynniki w q zawsze będą parzyste).Zatem algorytm polega na sprawdzeniu współczynników tych terminów x 2b i sprawdzeniu, czy któryś z nich jest większy niż 1. Jeśli nie ma żadnego, to nie ma równych odstępów 1s. Jeśli nie jest b , w
L
odniesieniu do których współczynnik x 2b jest większy niż 1, to wiemy, że pewne pary (A, C) - inny niż (B, B) - dla których a + c = 2b . Aby znaleźć rzeczywistą parę, po prostu wypróbowujemy każdą a inL
(odpowiadające c byłoby 2b-a ) i sprawdzamy, czy jest 1 na pozycji 2b-a inS
. Ten krok to O (n).To wszystko, ludzie.
Ktoś mógłby zapytać: czy musimy używać FFT? Wiele odpowiedzi, takich jak beta , flybywire i rsp , sugeruje, że podejście, które sprawdza każdą parę jedynek i sprawdza, czy jest 1 na „trzeciej” pozycji, może działać w O (n log n), w oparciu o intuicję że jeśli jest zbyt wiele jedynek, łatwo znaleźlibyśmy potrójną, a jeśli jest za mało jedynek, sprawdzenie wszystkich par zajmuje mało czasu. Niestety, chociaż ta intuicja jest słuszna, a proste podejście jest lepsze niż O (n 2 ), nie jest znacznie lepsze. Podobnie jak w odpowiedzi sdcvvc , możemy wziąć „zbiór podobny do Cantora” ciągów o długości n = 3 k , z 1 na pozycjach, których trójskładnikowa reprezentacja zawiera tylko 0 i 2 (bez 1). Taki ciąg ma 2 k = n (log 2) / (log 3) ≈ n 0,63 jedynek i nie ma równych 1s, więc sprawdzenie wszystkich par byłoby rzędu kwadratu liczby jedynek w nim: 4 k ≈ n 1,26, co niestety jest asymptotycznie dużo większe niż (n log n). W rzeczywistości najgorszy przypadek jest jeszcze gorszy: Leo Moser w 1953 roku skonstruował (skutecznie) takie struny, które mają n 1-c / √ (log n) 1s w sobie, ale nie są równo rozstawione 1s, co oznacza, że na takich strunach proste podejście wymagałoby Θ (n 2-2c / √ (log n) )- tylko maleńki nieco lepiej niż Θ (n = 2 ) , o dziwo!
Około maksymalnej liczby jedynek w ciągu o długości n bez 3 równomiernie rozmieszczonych (co widzieliśmy powyżej było co najmniej n 0,63 z łatwej konstrukcji podobnej do Cantora, a co najmniej n 1-c / √ (log n) z Mosera) - to OEIS A003002 . Można ją również obliczyć bezpośrednio z OEIS A065825 jako k tak, że A065825 (k) ≤ n <A065825 (k + 1). Napisałem program, żeby je znaleźć i okazuje się, że chciwy algorytm nie podaje najdłuższego takiego ciągu. Na przykład dla n = 9 możemy otrzymać 5 1s (110100011), ale chciwy daje tylko 4 (110110000), dla n = 26 możemy otrzymać 11 1s (11001010001000010110001101) ale chciwy daje tylko 8 (11011000011011000000000000), a za n = 74 możemy dostać 22 1s (1100001011000100000101101000100000000000000001000101101000001000110100000000) n = 74 możemy dostać 22 1s (1100001011000100000101101000100000000000000001000101101000001000110100000000) n = 74001100 Zgadzają się jednak w kilku miejscach do 50 (np. We wszystkich 38 do 50). Jak mówią referencje OEIS, wydaje się, że Jarosław Wróblewski jest zainteresowany tą kwestią i prowadzi stronę internetową na tych nieśrednianych zbiorach . Dokładne liczby znane są tylko do 194.
źródło
W artykule (1999) twój problem nazywa się ŚREDNIA :
Wikipedia :
To wystarczy, aby rozwiązać Twój problem :).
Co jest bardzo ważne jest to, że O (n log n) jest złożony w kategoriach liczby zer i jedynek, a nie liczba jedynek (który może być podawany w postaci tablicy, jak [1,5,9,15]). Sprawdzenie, czy zbiór ma ciąg arytmetyczny, wyrażony w liczbie jedynek, jest trudne i zgodnie z tym artykułem od 1999 roku nie jest znany algorytm szybszy niż O (n 2 ) i przypuszcza się, że nie istnieje. Każdy, kto tego nie bierze pod uwagę, próbuje rozwiązać otwarty problem.
Inne interesujące informacje, przeważnie niecodzienne:
Dolna granica:
Łatwą dolną granicą jest zbiór podobny do Cantora (liczby 1..3 ^ n-1 niezawierające 1 w swojej potrójnej rozwinięciu) - jego gęstość wynosi n ^ (log_3 2) (około 0,631). Zatem każde sprawdzenie, czy zbiór nie jest zbyt duży, a następnie sprawdzenie wszystkich par nie wystarczy, aby uzyskać O (n log n). Musisz mądrzej zbadać sekwencję. Cytujemy tutaj lepszą dolną granicę - to n 1-c / (log (n)) ^ (1/2) . Oznacza to, że zestaw Cantora nie jest optymalny.
Górna granica - mój stary algorytm:
Wiadomo, że dla dużego n podzbiór {1, 2, ..., n} nie zawierający progresji arytmetycznej ma co najwyżej n / (log n) ^ (1/20) elementów. Artykuł O trójek w ciągu arytmetycznym dowodzi więcej: zbiór nie może zawierać więcej niż n * 2 28 * (log log n / log n) 1/2 elementów. Możesz więc sprawdzić, czy ta granica jest osiągnięta, a jeśli nie, naiwnie sprawdzić pary. Jest to algorytm O (n 2 * log log n / log n), szybszy niż O (n 2 ). Niestety „O trójek…” jest na Springer - ale pierwsza strona jest dostępna, a ekspozycja Bena Greena jest dostępna tutaj , strona 28, twierdzenie 24.
Nawiasem mówiąc, prace pochodzą z 1999 roku - tego samego roku, co pierwszy, o którym wspomniałem, więc pewnie dlatego ten pierwszy nie wspomina o tym wyniku.
źródło
To nie jest rozwiązanie, ale sposób myślenia podobny do tego, co myślał Olexiy
Bawiłem się tworzeniem sekwencji z maksymalną liczbą jedynek i wszystkie są dość interesujące, mam do 125 cyfr, a oto pierwsze 3 liczby, które znalazłem, próbując wstawić jak najwięcej bitów „1”:
Zauważ, że wszystkie są fraktalami (nie jest to zbyt zaskakujące, biorąc pod uwagę ograniczenia). Być może jest coś w myśleniu wstecz, być może jeśli struna nie jest fraktalem o charakterystyce, to musi mieć powtarzający się wzór?
Dzięki beta za lepsze określenie tych liczb.
Aktualizacja: Niestety, wygląda na to, że wzór nie działa, gdy zaczyna się od wystarczająco dużego ciągu początkowego, takiego jak: 10000000000001:
źródło
Podejrzewam, że proste podejście, które wygląda jak O (n ^ 2), faktycznie da coś lepszego, jak O (n ln (n)). Sekwencje, których testowanie trwa najdłużej (dla danego n), to te, które nie zawierają trio, co nakłada poważne ograniczenia na liczbę jedynek, które mogą występować w sekwencji.
Wymyśliłem kilka argumentów machających rękami, ale nie udało mi się znaleźć porządnego dowodu. Zamierzam zaryzykować: odpowiedzią jest bardzo sprytny pomysł, o którym profesor wiedział od tak dawna, że wydaje się to oczywiste, ale dla uczniów jest to zbyt trudne. (Albo to, albo przespałeś wykład, który to dotyczył).
źródło
Aktualizacja: 2009-10-17 23:00
Uruchomiłem to na dużych liczbach (na przykład ciągach 20 milionów) i teraz uważam, że ten algorytm nie jest O (n logn). Mimo to jest to wystarczająco fajna implementacja i zawiera szereg optymalizacji, dzięki którym działa naprawdę szybko. Ocenia wszystkie układy ciągów binarnych 24 lub mniej cyfr w mniej niż 25 sekund.
Zaktualizowałem kod, aby uwzględnić
0 <= L < M < U <= X-1
obserwację z dzisiejszego dnia.Oryginalny
Jest to koncepcja podobna do innego pytania, na które odpowiedziałem . Ten kod sprawdził również trzy wartości w serii i określił, czy trójka spełnia warunek. Oto kod C # dostosowany z tego:
Główne różnice to:
Ten kod generuje zestaw danych umożliwiających znalezienie najtrudniejszych danych wejściowych do rozwiązania dla tego algorytmu.
Kod poprzedniego pytania wygenerował wszystkie rozwiązania za pomocą generatora Pythona. Ten kod wyświetla tylko najtrudniejsze dla każdej długości wzoru.
Ten kod sprawdza odległość od środkowego elementu do jego lewej i prawej krawędzi. Kod Pythona sprawdzał, czy suma była powyżej czy poniżej 0.
Bieżący kod działa od środka do krawędzi, aby znaleźć kandydata. Kod w poprzednim zadaniu działał od krawędzi do środka. Ta ostatnia zmiana daje dużą poprawę wydajności.
Na podstawie obserwacji pod koniec tego zapisu, kod przeszukuje pary parzystych liczb par liczb nieparzystych, aby znaleźć L i U, utrzymując M na stałym poziomie. Zmniejsza to liczbę wyszukiwań poprzez wstępne obliczenie informacji. W związku z tym kod wykorzystuje dwa poziomy pośrednictwa w głównej pętli FindCandidate i wymaga dwóch wywołań FindCandidate dla każdego środkowego elementu: raz dla liczb parzystych i raz dla nieparzystych.
Ogólną ideą jest praca na indeksach, a nie na surowej reprezentacji danych. Obliczenie tablicy, w której pojawiają się jedynki, pozwala algorytmowi działać w czasie proporcjonalnym do liczby jedynek w danych, a nie w czasie proporcjonalnym do długości danych. To jest standardowa transformacja: stwórz strukturę danych, która pozwoli na szybsze działanie przy zachowaniu równoważności problemu.
Wyniki są nieaktualne: usunięte.
Edycja: 16.10.2009 18:48
Na danych yx, którym da się wiarę w inne odpowiedzi, jako reprezentatywne dla danych trudnych do obliczenia, otrzymuję te wyniki ... Usunąłem je. Są nieaktualne.
Chciałbym zwrócić uwagę, że te dane nie są najtrudniejsze dla mojego algorytmu, więc myślę, że założenie, że fraktale yx są najtrudniejsze do rozwiązania, jest błędne. Spodziewam się, że najgorszy przypadek dla określonego algorytmu będzie zależał od samego algorytmu i prawdopodobnie nie będzie spójny w różnych algorytmach.
Edycja: 2009-10-17 13:30
Dalsze obserwacje na ten temat.
Najpierw przekonwertuj ciąg zer i jedynek na tablicę indeksów dla każdej pozycji jedynek. Powiedzmy, że długość tej tablicy A wynosi X. Wtedy celem jest znalezienie
takie że
lub
Ponieważ A [L] i A [U] sumują się do liczby parzystej, nie mogą być (parzyste, nieparzyste) ani (nieparzyste, parzyste). Wyszukiwanie dopasowania można ulepszyć, dzieląc A [] na pule nieparzyste i parzyste i wyszukując dopasowania na A [M] w pulach kandydatów nieparzystych i parzystych po kolei.
Myślę jednak, że jest to bardziej optymalizacja wydajności niż ulepszenie algorytmiczne. Liczba porównań powinna spaść, ale kolejność algorytmu powinna być taka sama.
Edytuj 2009-10-18 00:45
Przychodzi mi do głowy jeszcze inna optymalizacja, w tym samym duchu, co rozdzielenie kandydatów na parzyste i nieparzyste. Ponieważ trzy indeksy muszą dodać się do wielokrotności 3 (a, a + x, a + 2x - mod 3 wynosi 0, niezależnie od a i x), można oddzielić L, M i U do ich wartości mod 3 :
W rzeczywistości możesz połączyć to z obserwacją parzystych / nieparzystych i podzielić je na ich wartości mod 6:
i tak dalej. Zapewniłoby to dalszą optymalizację wydajności, ale nie przyspieszyłoby algorytmiczne.
źródło
Nie udało mi się jeszcze znaleźć rozwiązania :(, ale mam kilka pomysłów.
A co jeśli zaczniemy od odwrotnego problemu: skonstruuj sekwencję z maksymalną liczbą jedynek i BEZ równo rozmieszczonych trio. Jeśli możesz udowodnić, że maksymalna liczba jedynek wynosi o (n), możesz poprawić swoje oszacowanie, powtarzając tylko listę jedynek.
źródło
To może pomóc ...
Ten problem sprowadza się do następujących kwestii:
Na przykład, mając sekwencję of
[ 3, 5, 1, 3, 6, 5, 2, 2, 3, 5, 6, 4 ]
, znaleźlibyśmy podciąg[ 3, 6, 5, 2, 2]
z przedrostkiem[ 3, 6 ]
z sumą przedrostka9
i sufiksem[ 5, 2, 2 ]
z sumą sufiksów of9
.Redukcja jest następująca:
Na przykład, biorąc pod uwagę sekwencję
[ 0, 1, 1, 0, 0, 1, 0, 0, 0, 1 0 ]
, znaleźlibyśmy redukcję[ 1, 3, 4]
. Na podstawie tej redukcji obliczamy ciągły podciąg z[ 1, 3, 4]
, przedrostek[ 1, 3]
z sumą4
i przyrostek[ 4 ]
z sumą4
.Zmniejszenie to można obliczyć w
O(n)
.Niestety nie jestem pewien, dokąd się stąd udać.
źródło
Dla prostego typu problemu (tj. Wyszukujesz trzy „1” z tylko (tj. Zerem lub więcej) „0” między nimi), jest to całkiem proste: możesz po prostu podzielić sekwencję na każde „1” i poszukać dwóch sąsiednich podciągów mających tej samej długości (oczywiście drugi podciąg nie jest ostatnim). Oczywiście można to zrobić w czasie O (n) .
W przypadku bardziej złożonej wersji (tj. Przeszukujesz indeks i i lukę g > 0 taką, że
s[i]==s[i+g]==s[i+2*g]=="1"
), nie jestem pewien, czy istnieje rozwiązanie O (n log n) , ponieważ prawdopodobnie istnieje O (n²) trioli mających ta właściwość (pomyśl o ciągu wszystkich jedynek, takich trojaczków jest około n² / 2 ). Oczywiście szukasz tylko jednego z nich, ale obecnie nie mam pomysłu, jak go znaleźć ...źródło
Zabawne pytanie, ale kiedy zdasz sobie sprawę, że rzeczywisty wzorzec między dwoma „jedynkami” nie ma znaczenia, algorytm wygląda tak:
W kodzie, w stylu JTest (zauważ, że ten kod nie jest napisany jako najbardziej wydajny i dodałem kilka println, aby zobaczyć, co się stanie).
źródło
Pomyślałem o podejściu dziel i rządź, które mogłoby się sprawdzić.
Po pierwsze, w przetwarzaniu wstępnym musisz wstawić wszystkie liczby mniejsze niż połowa rozmiaru wejściowego ( n / 3) do listy.
Biorąc pod uwagę ciąg:
0000010101000100
(zwróć uwagę, że ten konkretny przykład jest prawidłowy)Wstaw wszystkie liczby pierwsze (i 1) od 1 do (16/2) do listy: {1, 2, 3, 4, 5, 6, 7}
Następnie podziel go na pół:
100000101 01000100
Rób to, aż dojdziesz do łańcuchów o rozmiarze 1. Dla wszystkich łańcuchów o rozmiarze jeden, które zawierają 1, dodaj indeks ciągu do listy możliwości; w przeciwnym razie zwraca -1 w przypadku niepowodzenia.
Musisz także zwrócić listę wciąż możliwych odległości odstępów, powiązanych z każdym indeksem początkowym. (Zacznij od listy, którą utworzyłeś powyżej i usuwaj liczby na bieżąco) W tym przypadku pusta lista oznacza, że masz do czynienia tylko z jedną 1, więc w tym momencie możliwe są dowolne odstępy; w przeciwnym razie lista zawiera odstępy, które należy wykluczyć.
Kontynuując powyższy przykład:
1000 0101 0100 0100
10 00 01 01 01 00 01 00
1 0 0 0 0 1 0 1 0 1 0 0 0 1 0 0
W pierwszym etapie łączenia mamy teraz osiem zestawów po dwa. W pierwszym mamy możliwość zbioru, ale dowiadujemy się, że odstęp o 1 jest niemożliwy, ponieważ jest tam drugie zero. Więc zwracamy 0 (dla indeksu) i {2,3,4,5,7} dla faktu, że odstępy o 1 są niemożliwe. W drugim nie mamy nic, więc zwracamy -1. W trzecim mamy dopasowanie bez usuniętych odstępów w indeksie 5, więc zwraca 5, {1,2,3,4,5,7}. W czwartej parze zwracamy 7, {1,2,3,4,5,7}. W piątym zwróć 9, {1, 2, 3, 4, 5, 7}. W szóstym zwróć -1. W siódmym zwróć 13, {1, 2, 3, 4, 5, 7}. W ósmym zwróć -1.
Łącząc ponownie w cztery zestawy po cztery, mamy:
1000
: Return (0, {4,5,6,7})0101
: Return (5, {2,3,4,5,6,7}), (7, {1,2,3,4,5,6 , 7})0100
: Return (9, {3,4,5,6,7})0100
: Return (13, {3,4,5,6,7})Łączenie w zestawy po osiem:
10000101
: Return (0, {5,7}), (5, {2,3,4,5,6,7}), (7, {1,2,3,4,5,6,7})01000100
: Zwrot (9, {4,7}), (13, {3,4,5,6,7})Łączenie w zestaw szesnastu:
10000101 01000100
W miarę postępów sprawdzamy wszystkie dotychczasowe możliwości. Aż do tego kroku zostawialiśmy rzeczy, które wykraczały poza koniec łańcucha, ale teraz możemy sprawdzić wszystkie możliwości.
Zasadniczo sprawdzamy pierwszą 1 z odstępami 5 i 7 i stwierdzamy, że nie pokrywają się one z 1. (Zwróć uwagę, że każdy test jest STAŁY, a nie jest czasem liniowym) Następnie sprawdzamy drugi (indeks 5) z odstępami 2, 3, 4, 5, 6 i 7 - lub moglibyśmy, ale możemy zatrzymać się na 2, ponieważ to faktycznie pasuje.
Uff! To dość długi algorytm.
Nie wiem w 100%, czy to O (n log n) z powodu ostatniego kroku, ale wszystko, co tam jest, jest zdecydowanie O (n log n), o ile wiem. Wrócę do tego później i spróbuję udoskonalić ostatni krok.
EDYCJA: Zmieniłem moją odpowiedź, aby odzwierciedlić komentarz Welboga. Przepraszamy za błąd. Pseudokod napiszę później, gdy będę miał trochę więcej czasu na rozszyfrowanie tego, co napisałem ponownie. ;-)
źródło
100010001
? Jeśli dobrze zrozumiem twoje podejście, nie będzie w stanie go dopasować, ponieważ poprawnej odpowiedzi(0,{4})
nie można obliczyć. Biorąc pod uwagę, że na swojej liście potrzebujesz liczb innych niż liczby pierwsze, myślę, że łatwo jest wymyślić patologiczne ciągi, które zawyżają listy możliwości, które musisz sprawdzić, do wyższych niż O (n log (n)).Podam tutaj moje przybliżone przypuszczenie i pozwolę tym, którzy są lepsi w obliczaniu złożoności, aby pomogli mi w tym, jak mój algorytm radzi sobie w notacji O
Nie mam pojęcia, jak obliczyć złożoność tego, czy ktoś może pomóc?
edycja: dodaj kod ilustrujący mój pomysł
edit2: próbowałem skompilować mój kod i znalazłem kilka poważnych błędów, naprawiono
źródło
Wymyśliłem coś takiego:
Inspiruje to andycjw.
Jeśli chodzi o złożoność, może to być O (nlogn), ponieważ w każdej rekursji dzielimy przez dwa.
Mam nadzieję, że to pomoże.
źródło
Ok, zamierzam ponownie zająć się problemem. Myślę, że mogę udowodnić algorytm O (n log (n)), który jest podobny do tych już omówionych, używając zbalansowanego drzewa binarnego do przechowywania odległości między 1. Podejście to zostało zainspirowane obserwacją Justice'a, że problem sprowadza się do listy odległości między jedynkami.
Czy moglibyśmy przeskanować ciąg wejściowy, aby skonstruować zrównoważone drzewo binarne wokół pozycji 1, tak że każdy węzeł przechowuje pozycję 1, a każda krawędź jest oznaczona odległością do sąsiedniej 1 dla każdego węzła podrzędnego. Na przykład:
Można to zrobić w O (n log (n)), ponieważ dla łańcucha o rozmiarze n każde wstawienie przyjmuje O (log (n)) w najgorszym przypadku.
Następnie problem polega na przeszukaniu drzewa, aby odkryć, czy w jakimkolwiek węźle istnieje ścieżka od tego węzła do lewego dziecka, która ma taką samą odległość, jak ścieżka przez prawe dziecko. Można to zrobić rekurencyjnie na każdym poddrzewie. Łącząc dwa poddrzewa w wyszukiwaniu, musimy porównać odległości od ścieżek w lewym poddrzewie z odległościami od ścieżek w prawym. Ponieważ liczba ścieżek w poddrzewie będzie proporcjonalna do log (n), a liczba węzłów to n, uważam, że można to zrobić w czasie O (n log (n)).
Czy coś przegapiłem?
źródło
Wydawało się, że to fajny problem, więc postanowiłem spróbować swoich sił.
Zakładam, że 111000001 znajdzie pierwsze 3 i odniesie sukces. Zasadniczo liczba zer po 1 jest ważna, ponieważ 0111000 to to samo, co 111000 zgodnie z twoją definicją. Gdy znajdziesz dwa przypadki z 1, następny znaleziony 1 kończy trylogię.
Tutaj jest w Pythonie:
To jest pierwsza próba, więc jestem pewien, że można to napisać w bardziej przejrzysty sposób. Proszę wymienić poniżej przypadki, w których ta metoda zawodzi.
źródło
Zakładam, że powodem tego jest nlog (n) jest następujący:
Więc masz n, log (n) i 1 ... O (nlogn)
Edycja: Ups, moja wina. Mój mózg ustawił, że n / 2 zostało zarejestrowane ... co oczywiście nie jest (podwojenie liczby pozycji nadal podwaja liczbę iteracji w pętli wewnętrznej). To wciąż jest n ^ 2, nie rozwiązując problemu. Cóż, przynajmniej muszę napisać jakiś kod :)
Wdrożenie w Tcl
źródło
Myślę, że znalazłem sposób na rozwiązanie problemu, ale nie mogę skonstruować formalnego dowodu. Rozwiązanie, które stworzyłem, jest napisane w Javie i używa licznika „n”, aby policzyć, ile list / tablic uzyskuje dostęp. Zatem n powinno być mniejsze lub równe stringLength * log (stringLength), jeśli jest poprawne. Wypróbowałem to dla liczb od 0 do 2 ^ 22 i działa.
Rozpoczyna się iteracją po ciągu wejściowym i utworzeniem listy wszystkich indeksów, które zawierają jedynkę. To jest po prostu O (n).
Następnie z listy indeksów wybiera firstIndex i secondIndex, który jest większy niż pierwszy. Te dwa indeksy muszą zawierać jedynki, ponieważ znajdują się na liście indeksów. Stamtąd można obliczyć thirdIndex. Jeśli inputString [thirdIndex] ma wartość 1, to zatrzymuje się.
}
uwaga dodatkowa: licznik n nie jest zwiększany, gdy iteruje po ciągu wejściowym w celu skonstruowania listy indeksów. Ta operacja to O (n), więc i tak nie będzie miała wpływu na złożoność algorytmu.
źródło
O(n^2)
algorytm.Jednym z wejść w problem jest myślenie o czynnikach i zmianach.
Dzięki przesunięciu porównujesz ciąg jedynek i zer z przesuniętą wersją samego siebie. Następnie bierzesz pasujące. Weź ten przykład przesunięty o dwa:
Wynikowe jedynki (bitowe i połączone operatorem logicznym) muszą reprezentować wszystkie jedynki, które są równo rozdzielone przez dwa. Ten sam przykład przesunięty o trzy:
W tym przypadku nie ma 1, które są równomiernie oddalone od siebie o trzy.
Więc co ci to mówi? Cóż, wystarczy przetestować przesunięcia, które są liczbami pierwszymi. Na przykład, powiedzmy, że masz dwie jedynki, które różnią się od siebie o sześć. Musiałbyś przetestować tylko „dwie” zmiany i „trzy” zmiany (ponieważ dzielą one sześć). Na przykład:
Zatem jedyne przesunięcia, jakie kiedykolwiek musisz sprawdzić, to 2,3,5,7,11,13 itd. Aż do liczby pierwszej najbliższej pierwiastkowi kwadratowemu z rozmiaru ciągu cyfr.
Prawie rozwiązany?
Myślę, że jestem bliżej rozwiązania. Gruntownie:
Myślę, że największą wskazówką do odpowiedzi jest to, że najszybszymi algorytmami sortowania są O (n * log (n)).
ŹLE
Krok 1 jest błędny, jak wskazał kolega. Gdybyśmy mieli jedynki w pozycji 2,12 i 102. Wtedy przyjmując moduł 10, wszystkie miałyby te same reszty, ale nie byłyby jednakowo oddalone od siebie! Przepraszam.
źródło
Oto kilka myśli, które mimo moich najlepszych starań nie wydają się zawijać w łuk. Mimo to mogą być przydatnym punktem wyjścia do czyjejś analizy.
Rozważ zaproponowane rozwiązanie w następujący sposób, które jest podejściem, które sugerowało kilka osób, w tym ja we wcześniejszej wersji tej odpowiedzi.
:)
Teraz rozważ ciągi wejściowe, takie jak następujące, które nie będą miały rozwiązania:
Ogólnie jest to konkatenacja k ciągów w postaci j 0, po której następuje 1 dla j od zera do k-1.
Zauważ, że długości podciągów wynoszą 1, 2, 3 itd. Zatem problem z rozmiarem n ma podciągi o długościach od 1 do k takie, że n = k (k + 1) / 2.
Zauważ, że k śledzi również liczbę jedynek, które musimy wziąć pod uwagę. Pamiętaj, że za każdym razem, gdy widzimy 1, musimy wziąć pod uwagę wszystkie 1 widziane do tej pory. Więc kiedy widzimy drugie 1, rozważamy tylko pierwsze, kiedy widzimy trzecie 1, rozważamy ponownie pierwsze dwa, kiedy widzimy czwartą 1, musimy ponownie rozważyć pierwsze trzy i tak dalej. Pod koniec algorytmu uwzględniliśmy k (k-1) / 2 par jedynek. Nazwij to p.
Zależność między n i p jest taka, że n = p + k.
Proces przechodzenia przez strunę zajmuje O (n) czasu. Za każdym razem, gdy napotkano 1, wykonywanych jest maksymalnie (k-1) porównań. Ponieważ n = k (k + 1) / 2, n> k ** 2, więc sqrt (n)> k. To daje nam O (n sqrt (n)) lub O (n ** 3/2). Zauważ jednak, że może to nie być naprawdę ścisłe ograniczenie, ponieważ liczba porównań waha się od 1 do maksimum k, przez cały czas nie jest to k. Ale nie jestem pewien, jak to wyjaśnić w matematyce.
To nadal nie jest O (n log (n)). Nie mogę też udowodnić, że te dane wejściowe są najgorszymi przypadkami, chociaż podejrzewam, że tak. Myślę, że gęstsze upakowanie 1 z przodu skutkuje jeszcze rzadszym upakowaniem na końcu.
Ponieważ ktoś może nadal uznać to za przydatne, oto mój kod dla tego rozwiązania w Perlu:
źródło
Podczas skanowania 1, dodaj ich pozycje do listy. Dodając drugą i kolejne 1, porównaj je z każdą pozycją na liście do tej pory. Odstępy są równe currentOne (w środku) - previousOne (po lewej). Bit po prawej stronie to currentOne + odstęp. Jeśli jest 1, koniec.
Lista tych rośnie odwrotnie wraz z przestrzenią między nimi. Mówiąc prosto, jeśli masz dużo zer między 1 (jak w najgorszym przypadku), twoja lista znanych 1 będzie rosła dość wolno.
źródło
Pomyślałem, że dodam jeden komentarz przed zamieszczeniem 22. naiwnego rozwiązania problemu. W przypadku naiwnego rozwiązania nie musimy pokazywać, że liczba jedynek w ciągu wynosi co najwyżej O (log (n)), ale raczej że jest to najwyżej O (sqrt (n * log (n)).
Solver:
Zasadniczo jest trochę podobny do pomysłu i implementacji flybywire, ale patrzy w przyszłość, a nie wstecz.
Greedy String Builder:
(Na swoją obronę, wciąż jestem na etapie rozumienia języka Python)
Ponadto, potencjalnie użyteczny wynik chciwego budowania strun, występuje raczej konsekwentny skok po uderzeniu w potęgę 2 w liczbie jedynek ... których nie byłem skłonny czekać, aż zobaczyłem uderzenie 2096.
źródło
Postaram się przedstawić podejście matematyczne. To bardziej początek niż koniec, więc każda pomoc, komentarz, a nawet sprzeczność - będą bardzo mile widziane. Jeśli jednak to podejście jest sprawdzone - algorytm polega na prostym wyszukiwaniu w ciągu.
Biorąc pod uwagę ustaloną liczbę spacji
k
i ciągS
, wyszukiwanie trioli k- spacji trwaO(n)
- po prostu testujemy dla każdego warunku0<=i<=(n-2k)
ifS[i]==S[i+k]==S[i+2k]
. Test trwaO(1)
i robimy ton-k
razy, gdziek
jest stała, więc trwaO(n-k)=O(n)
.Załóżmy, że istnieje odwrotna proporcja między liczbą
1
's a maksymalną liczbą przestrzeni, których musimy szukać. To znaczy, że jeśli jest ich wiele1
, musi istnieć trójka i musi być dość gęsta; Jeśli jest tylko kilka1
, trójka (jeśli w ogóle) może być dość rzadka. Innymi słowy, mogę udowodnić, że jeśli mam wystarczająco dużo1
, taka trójka musi istnieć - a im więcej1
mam, tym trzeba znaleźć triolę gęstszą. Można to wytłumaczyć zasadą Pigeonhole - Mam nadzieję, że omówię to później.Powiedz, że ma górną granicę
k
możliwej liczby miejsc, których muszę szukać. Teraz, każdy1
znajduje sięS[i]
musimy sprawdzić1
wS[i-1]
iS[i+1]
,S[i-2]
iS[i+2]
...S[i-k]
iS[i+k]
. Trwa toO((k^2-k)/2)=O(k^2)
dla każdego1
wS
- ze względu na wzór sumowania serii Gaussa . Zauważ, że różni się to od sekcji 1 - mamk
jako górną granicę liczby spacji, a nie jako stałą przestrzeń.Musimy to udowodnić
O(n*log(n))
. Oznacza to, że musimy pokazać, żek*(number of 1's)
jest to proporcjonalne dolog(n)
.Jeśli możemy to zrobić, algorytm jest trywialny - dla każdego,
1
wS
którego indeksie jesti
, po prostu szukaj1
znaków z każdej strony aż do odległościk
. Jeśli znaleziono dwa w tej samej odległości, wróći
ik
. Ponownie, najtrudniejsza część polegałaby na znalezieniuk
i udowodnieniu poprawności.Byłbym bardzo wdzięczny za twoje komentarze tutaj - próbowałem znaleźć zależność między
k
a liczbą1
na mojej tablicy, jak dotąd bez powodzenia.źródło
Założenie:
Po prostu źle, mówiąc o log (n) liczbie górnej granicy jedynek
EDYTOWAĆ:
Teraz odkryłem, że używając liczb Cantora (jeśli są poprawne), gęstość na zbiorze wynosi (2/3) ^ Log_3 (n) (co za dziwna funkcja) i zgadzam się, gęstość log (n) / n jest zbyt silna.
Jeśli jest to górna granica, istnieje algorytm, który rozwiązuje ten problem w złożoności czasowej co najmniej O (n * (3/2) ^ (log (n) / log (3))) i O ((3/2) ^ ( log (n) / log (3))) złożoność przestrzeni. (sprawdź odpowiedź Justice na algorhitm)
To wciąż jest o wiele lepsze niż O (n ^ 2)
Ta funkcja ((3/2) ^ (log (n) / log (3))) naprawdę wygląda na pierwszy rzut oka jak n * log (n).
Jak otrzymałem tę formułę?
Umieszczanie numeru Cantors na sznurku.
Załóżmy, że długość łańcucha wynosi 3 ^ p == n
Na każdym etapie tworzenia łańcucha Cantor zachowujesz 2/3 dotychczasowej liczby jedynek. Zastosuj to p razy.
To oznacza (n * ((2/3) ^ p)) -> (((3 ^ p)) * ((2/3) ^ p)) pozostałe i po uproszczeniu 2 ^ p. To oznacza 2 ^ p jedynek w 3 ^ p łańcuchach -> (3/2) ^ p jedynek. Podstaw p = log (n) / log (3) i pobierz
((3/2) ^ (log (n) / log (3)))
źródło
A co z prostym rozwiązaniem O (n), z przestrzenią O (n ^ 2)? (Przyjmuje założenie, że wszystkie operatory bitowe działają w O (1).)
Algorytm zasadniczo działa w czterech etapach:
Etap 1: Dla każdego bitu w pierwotnej liczbie sprawdź, jak daleko są one, ale rozważ tylko jeden kierunek. (Rozważyłem wszystkie bity w kierunku najmniej znaczącego bitu.)
Etap 2: Odwróć kolejność bitów na wejściu;
Etap 3: Ponownie wykonaj krok 1 na wejściu odwróconym.
Etap 4: Porównaj wyniki z Etapu 1 i Etapu 3. Jeśli jakiekolwiek bity są równo rozmieszczone powyżej ORAZ poniżej, musimy mieć trafienie.
Należy pamiętać, że żaden krok w powyższym algorytmie nie trwa dłużej niż O (n). ^ _ ^
Dodatkową korzyścią jest to, że algorytm ten znajdzie WSZYSTKIE równomiernie rozmieszczone z KAŻDEJ liczby. Na przykład, jeśli otrzymasz wynik „0x0005”, to są równo rozmieszczone jednostki w odległości 1 i 3 jednostek
Tak naprawdę nie próbowałem optymalizować poniższego kodu, ale jest to kompilowalny kod C #, który wydaje się działać.
Ktoś prawdopodobnie skomentuje, że dla żadnej wystarczająco dużej liczby operacje bitowe nie mogą być wykonywane w O (1). Miałbyś rację. Przypuszczam jednak, że każde rozwiązanie, które wykorzystuje dodawanie, odejmowanie, mnożenie lub dzielenie (czego nie można zrobić przez przesuwanie), również miałoby ten problem.
źródło
Poniżej znajduje się rozwiązanie. Tu i tam mogą wystąpić drobne błędy, ale pomysł jest rozsądny.
Edycja: to nie jest n * log (n)
PSEUDO KOD:
Kod C #:
Jak to działa:
źródło
Oczywiście musimy przynajmniej sprawdzać pęczki trojaczków w tym samym czasie, więc musimy jakoś skompresować czeki. Mam algorytm kandydata, ale analiza złożoności czasowej przekracza mój próg czasowy *.
Zbuduj drzewo, w którym każdy węzeł ma troje dzieci, a każdy węzeł zawiera całkowitą liczbę jedynek na swoich liściach. Zbuduj również połączoną listę ponad 1. Przypisz każdemu węzłowi dozwolony koszt proporcjonalny do zakresu, który obejmuje. Dopóki czas spędzony w każdym węźle mieści się w budżecie, będziemy mieć algorytm O (n lg n).
-
Zacznij od korzenia. Jeśli kwadrat całkowitej liczby jedynek poniżej jest mniejszy niż dopuszczalny koszt, zastosuj naiwny algorytm. W przeciwnym razie powtórz na swoich dzieciach.
Teraz albo wróciliśmy w ramach budżetu, albo wiemy, że w jednym z dzieci nie ma żadnych prawidłowych trojaczków. Dlatego musimy sprawdzić trójki między węzłami.
Teraz robi się niesamowicie bałagan. Zasadniczo chcemy powtórzyć potencjalne zestawy dzieci, jednocześnie ograniczając zakres. Gdy tylko zakres jest na tyle ograniczony, że naiwny algorytm będzie działał w ramach budżetu, robisz to. Ciesz się wdrażaniem tego, bo gwarantuję, że będzie to żmudne. Jest z tuzin przypadków.
-
Uważam, że algorytm zadziała, ponieważ sekwencje bez prawidłowych trojaczków wydają się przechodzić naprzemiennie między pęczkami jedynek i wieloma zerami. Skutecznie dzieli pobliską przestrzeń wyszukiwania, a drzewo naśladuje ten podział.
Czas działania algorytmu wcale nie jest oczywisty. Opiera się na nietrywialnych właściwościach sekwencji. Jeśli 1 są naprawdę rzadkie, naiwny algorytm będzie działał w ramach budżetu. Jeśli jedynki są gęste, należy od razu znaleźć dopasowanie. Ale jeśli gęstość jest „w sam raz” (np. Blisko ~ n ^ 0,63, co można osiągnąć ustawiając wszystkie bity na pozycjach bez cyfry „2” w bazie 3), nie wiem, czy zadziała. Musiałbyś udowodnić, że efekt rozszczepiania jest wystarczająco silny.
źródło
Nie ma tu teoretycznej odpowiedzi, ale napisałem szybki program w języku Java, aby zbadać zachowanie w czasie wykonywania w funkcji k i n, gdzie n to całkowita długość bitu, ak to liczba jedynek. Jestem z kilkoma odpowiadającymi, którzy twierdzą, że „zwykły” algorytm, który sprawdza wszystkie pary pozycji bitów i szuka trzeciego bitu, mimo że wymagałby O (k ^ 2) w najgorszym przypadku, w rzeczywistość, ponieważ najgorszy przypadek wymaga rzadkich bitstringów, to O (n ln n).
W każdym razie oto program poniżej. Jest to program w stylu Monte-Carlo, który przeprowadza dużą liczbę prób NTRIALS dla stałej n i losowo generuje zestawy bitów dla zakresu wartości k przy użyciu procesów Bernoulliego z gęstością jedynkową ograniczoną między limitami, które można określić, i rejestruje czas wykonywania znalezienia lub niepowodzenia znalezienia trójki równomiernie rozmieszczonych, czas mierzony w krokach, a NIE w czasie procesora. Uruchomiłem go dla n = 64, 256, 1024, 4096, 16384 * (nadal działa), najpierw test z 500000 prób, aby zobaczyć, które wartości k zajmują najdłuższy czas działania, a następnie kolejny test z 5000000 prób z zawężonymi próbami- skup się na gęstości, aby zobaczyć, jak wyglądają te wartości. Najdłuższe czasy pracy zdarzają się przy bardzo rzadkiej gęstości (np. Dla n = 4096 szczyty czasu pracy znajdują się w zakresie k = 16-64, z łagodnym szczytem dla średniego czasu pracy przy 4212 krokach przy k = 31, maksymalny czas pracy osiągnął poziom 5101 kroków przy k = 58). Wygląda na to, że dla najgorszego kroku O (k ^ 2) wymagałoby to bardzo dużych wartości N, aby były większe niż krok O (n), w którym przeszukuje się ciąg bitów, aby znaleźć indeksy pozycji 1.
źródło
Mam problem z najgorszym scenariuszem z milionami cyfr. Fuzzing od
/dev/urandom
zasadniczo daje O (n), ale wiem, że najgorszy przypadek jest gorszy. Po prostu nie mogę powiedzieć, o ile gorzej. W przypadku małychn
, znalezienie danych wejściowych w pobliżu jest trywialne3*n*log(n)
, ale zaskakująco trudno jest odróżnić je od innej kolejności wzrostu dla tego konkretnego problemu.Czy ktoś, kto pracował nad danymi wejściowymi z najgorszego przypadku, może wygenerować ciąg o długości większej niż, powiedzmy, sto tysięcy?
źródło
Możliwa jest adaptacja algorytmu Rabina-Karpa. Jego złożoność wynosi 0 (n), więc może ci pomóc.
Spójrz na http://en.wikipedia.org/wiki/Rabin-Karp_string_search_algorithm
źródło
Czy to może być rozwiązanie? I ', nie jestem pewien, czy to O (nlogn), ale moim zdaniem jest lepsze niż O (n²), ponieważ jedynym sposobem, aby nie znaleźć potrójnej, byłby rozkład liczb pierwszych.
Jest miejsce na ulepszenia, druga znaleziona 1 może być następną pierwszą 1. Również bez sprawdzania błędów.
źródło
Myślę, że ten algorytm ma złożoność O (n log n) (C ++, DevStudio 2k5). Teraz nie znam szczegółów, jak analizować algorytm w celu określenia jego złożoności, więc dodałem do kodu pewne metryki zbierające informacje. Kod zlicza liczbę testów wykonanych na sekwencji jedynek i zer dla dowolnego podanego wejścia (mam nadzieję, że nie zrobiłem kulek z algorytmu). Możemy porównać rzeczywistą liczbę testów z wartością O i sprawdzić, czy istnieje korelacja.
Ten program wyprowadza liczbę testów dla każdego łańcucha o długości do 32 znaków. Oto wyniki:
Dodałem również wartości „n log n”. Wykreśl je za pomocą wybranego narzędzia graficznego, aby zobaczyć korelację między dwoma wynikami. Czy ta analiza obejmuje wszystkie wartości n? Nie wiem
źródło