Niedawno miałem ciekawe doświadczenie w rozmowie kwalifikacyjnej. Pytanie zaczęło się naprawdę łatwo:
Q1 : Mamy torbę zawierającą numery
1
,2
,3
, ...,100
. Każda liczba pojawia się dokładnie raz, więc jest 100 liczb. Teraz jedna liczba jest losowo wybierana z torby. Znajdź brakujący numer.
Oczywiście wcześniej słyszałem to pytanie, więc bardzo szybko odpowiedziałem w następujący sposób:
A1 : Cóż, suma liczb
1 + 2 + 3 + … + N
to(N+1)(N/2)
(patrz Wikipedia: suma szeregów arytmetycznych ). BoN = 100
suma to5050
.Tak więc, jeśli wszystkie liczby są obecne w torbie, suma będzie dokładnie
5050
. Ponieważ brakuje jednej liczby, suma będzie mniejsza niż ta, a różnicą jest ta liczba. Możemy więc znaleźć tę brakującą liczbę wO(N)
czasie iO(1)
przestrzeni.
W tym momencie myślałem, że dobrze mi poszło, ale nagle pytanie przybrało nieoczekiwany obrót:
Q2 : Zgadza się, ale teraz jak byś to zrobił, gdyby DWIE liczby brakowały?
Nigdy wcześniej nie widziałem / nie słyszałem / nie rozważałem tej odmiany, więc spanikowałem i nie mogłem odpowiedzieć na pytanie. Ankieter nalegał na zapoznanie się z moim procesem myślowym, więc wspomniałem, że być może możemy uzyskać więcej informacji, porównując z oczekiwanym produktem, lub może wykonując drugie przejście po zebraniu informacji z pierwszego przejścia itp., Ale tak naprawdę to po prostu strzelałem w ciemności, zamiast mieć właściwie ścieżkę do rozwiązania.
Ankieter próbował mnie zachęcić, mówiąc, że posiadanie drugiego równania jest rzeczywiście jednym ze sposobów rozwiązania problemu. W tym momencie byłem trochę zdenerwowany (za to, że nie znałem wcześniej odpowiedzi) i zapytałem, czy to jest ogólna (czytaj: „przydatna”) technika programowania, czy może to tylko odpowiedź na podstęp / gotcha.
Zaskoczyła mnie odpowiedź ankietera: możesz uogólnić technikę, by znaleźć 3 brakujące liczby. W rzeczywistości możesz go uogólnić, aby znaleźć k brakujących liczb.
Pytanie : Jeśli dokładnie brakuje liczby k z torby, jak byś ją skutecznie znalazł?
To było kilka miesięcy temu i nadal nie mogłem zrozumieć, co to za technika. Oczywiście nie jest to Ω(N)
czas dolna granica ponieważ musimy skanować wszystkie numery co najmniej raz, ale wywiad podkreślał, że CZAS i PRZESTRZEŃ złożoność techniki rozwiązywania (minus O(N)
skanowania wejściowego czasu) jest zdefiniowana w k nie N .
Pytanie jest proste:
- Jak rozwiązałbyś Q2 ?
- Jak rozwiązałbyś Q3 ?
- Jak rozwiązałbyś Qk ?
Wyjaśnienia
- Generalnie jest N liczb od 1 .. N , nie tylko 1..100.
- Nie szukam oczywistego rozwiązania opartego na zestawie, np. Używając zestawu bitów , kodując obecność / nieobecność każdej liczby wartością wyznaczonego bitu, a zatem używając
O(N)
bitów w dodatkowej przestrzeni. Nie możemy sobie pozwolić na dodatkową przestrzeń proporcjonalna do N . - Nie szukam też oczywistego podejścia do sortowania. To i podejście oparte na zestawie warto wspomnieć w wywiadzie (są łatwe do wdrożenia, aw zależności od N mogą być bardzo praktyczne). Szukam rozwiązania Świętego Graala (które może, ale nie musi być praktyczne do wdrożenia, ale mimo to ma pożądane cechy asymptotyczne).
Więc znowu, oczywiście, musisz zeskanować dane wejściowe O(N)
, ale możesz przechwycić tylko niewielką ilość informacji (zdefiniowaną w kategoriach k nie N ), a następnie jakoś znaleźć k brakujących liczb.
XOR
wszystkich liczb od1
don
, a następnie xoring wynik ze wszystkimi liczbami w podanej tablicy. W końcu brakuje ci numeru. W tym rozwiązaniu nie musisz przejmować się przepełnieniem, jak w podsumowaniu.Odpowiedzi:
Oto podsumowanie linku Dimitrisa Andreou .
Pamiętaj sumę i-tych mocy, gdzie i = 1,2, .., k. Zmniejsza to problem do rozwiązania układu równań
a 1 + a 2 + ... + a k = b 1
a 1 2 + a 2 2 + ... + a k 2 = b 2
...
a 1 k + a 2 k + ... + a k k = b k
Korzystanie tożsamości Newtona , wiedząc, b i pozwala obliczyć
c 1 = a 1 + a 2 + ... a k
c 2 = a 1 a 2 + a 1 a 3 + ... + a k-1 a k
...
c k = a 1 a 2 ... a k
Jeśli rozszerzysz wielomian (xa 1 ) ... (xa k ), współczynniki będą wynosić dokładnie c 1 , ..., c k - patrz wzory Viète . Ponieważ każdy czynnik wielomianu jest jednoznacznie (pierścień wielomianów jest domeną euklidesową ), oznacza to, że i są jednoznacznie określone, aż do permutacji.
To kończy dowód na to, że zapamiętywanie mocy wystarcza do odzyskania liczb. Dla stałego k jest to dobre podejście.
Jednak gdy k jest zmienne, bezpośrednie podejście do obliczania c 1 , ..., c k jest zbyt drogie, ponieważ np. C k jest iloczynem wszystkich brakujących liczb, wielkości n! / (Nk) !. Aby temu zaradzić, wykonaj obliczenia w polu Z q , gdzie q jest liczbą pierwszą taką, że n <= q <2n - istnieje zgodnie z postulatem Bertranda . Dowodu nie trzeba zmieniać, ponieważ formuły wciąż się utrzymują, a rozkład na czynniki wielomianowe jest nadal wyjątkowy. Potrzebujesz również algorytmu faktoryzacji nad polami skończonymi, na przykład algorytmu Berlekamp lub Cantor-Zassenhaus .
Pseudokod wysokiego poziomu dla stałej k:
Aby zmienić k, znajdź liczbę pierwszą n <= q <2n za pomocą np. Millera-Rabina i wykonaj kroki ze wszystkimi liczbami zmniejszonymi modulo q.
EDYCJA: Poprzednia wersja tej odpowiedzi stwierdzała, że zamiast Z q , gdzie q jest liczbą pierwszą, możliwe jest użycie pola skończonego o charakterystyce 2 (q = 2 ^ (log n)). Tak nie jest, ponieważ formuły Newtona wymagają dzielenia przez liczby do k.
źródło
q = 2^(log n)
. (Jak udało ci się zrobić super- i indeksy dolne ?!)O(N^2)
rozwiązanie prawdopodobnie przewyższy to piękno nawet na dość wysokim poziomieN
. Sprawia, że myślę o tym: tinyurl.com/c8fwgw Niemniej jednak świetna robota! Nie miałbym cierpliwości, by czołgać się przez całą matematykę :)hash set
ai iteracja po1...N
pakiecie za pomocą odnośników w celu ustalenia, czy brakuje numerów, byłoby najbardziej ogólne, najszybsze średnio pod względemk
odmian, najbardziej debugowalne, najbardziej łatwe do utrzymania i zrozumiałe rozwiązanie. Oczywiście sposób matematyczny jest imponujący, ale gdzieś po drodze musisz być inżynierem, a nie matematykiem. Zwłaszcza, gdy w grę wchodzi biznes.Znajdziesz go, czytając kilka stron Muthukrishnan - Algorytmy strumienia danych: Puzzle 1: Znajdowanie brakujących liczb . Pokazuje dokładnie uogólnienie, którego szukasz . Prawdopodobnie to właśnie przeczytał twój ankieter i dlaczego zadał te pytania.
Teraz, gdyby tylko ludzie zaczęli usuwać odpowiedzi, które zostały zastąpione lub zastąpione przez leczenie Muthukrishnana, i ułatwiłyby znalezienie tego tekstu. :)
Zobacz także bezpośrednio spokrewnioną odpowiedź sdcvvc , która zawiera również pseudokod (hurra! Nie trzeba czytać tych skomplikowanych formuł matematycznych :)) (dzięki, świetna robota!).
źródło
Możemy rozwiązać Q2, sumując zarówno same liczby, jak i kwadraty liczb.
Możemy wtedy zredukować problem do
Gdzie
x
iy
jak daleko sumy są poniżej oczekiwanych wartości.Zastąpienie daje nam:
Które możemy następnie rozwiązać, aby określić nasze brakujące liczby.
źródło
Jak zauważył @j_random_hacker, jest to dość podobne do znajdowania duplikatów w czasie O (n) i przestrzeni O (1) , tutaj też działa adaptacja mojej odpowiedzi.
Zakładając, że „torba” jest reprezentowana przez tablicę
A[]
wielkości opartą na 1N - k
, możemy rozwiązać Qk wO(N)
czasie iO(k)
dodatkowej przestrzeni.Po pierwsze, rozszerzamy naszą tablicę
A[]
ok
elementy, aby była teraz wielkościN
. To jestO(k)
dodatkowa przestrzeń. Następnie uruchamiamy następujący algorytm pseudokodowy:Pierwsza pętla inicjuje
k
dodatkowe wpisy do tego samego, co pierwszy wpis w tablicy (jest to po prostu wygodna wartość, o której wiemy, że jest już w tablicy - po tym kroku wszelkie wpisy, których brakowało w początkowej tablicy rozmiaru,N-k
są wciąż brakuje w rozszerzonej tablicy).Druga pętla permutuje rozszerzoną tablicę, więc jeśli element
x
jest obecny co najmniej raz, to jeden z tych wpisów będzie na pozycjiA[x]
.Zauważ, że chociaż ma zagnieżdżoną pętlę, nadal działa w
O(N)
czasie - zamiana występuje tylko wtedy, gdy istniejei
takaA[i] != i
, a każda zamiana ustawia co najmniej jeden element takiA[i] == i
, w którym wcześniej nie było to prawdą. Oznacza to, że łączna liczba zamian (a tym samym łączna liczba wykonańwhile
ciała pętli) wynosi co najwyżejN-1
.Trzecia pętla wypisuje te indeksy tablicy
i
, które nie są zajęte przez wartośći
- oznacza to, żei
musiała jej brakować.źródło
A[i]
, co oznacza, że następna iteracja nie będzie porównywać tych samych dwóch wartości, co poprzednia. NowaA[i]
będzie taka sama jak w ostatniej pętliA[A[i]]
, ale nowaA[A[i]]
będzie nową wartością. Wypróbuj i przekonaj się.Poprosiłem czterolatka o rozwiązanie tego problemu. Sortował liczby, a potem liczył dalej. Wymaga miejsca O (podłoga kuchenna) i działa równie łatwo, jakkolwiek brakuje wielu kulek.
źródło
Nie jestem pewien, czy jest to najbardziej wydajne rozwiązanie, ale zapętlibym wszystkie wpisy i użyję zestawu bitów, aby zapamiętać, które liczby są ustawione, a następnie przetestować pod kątem 0 bitów.
Lubię proste rozwiązania - a nawet wierzę, że może to być szybsze niż obliczenie sumy lub sumy kwadratów itp.
źródło
O(N)
ani sposobu liczenia, ani sortowaniaO(N log N)
porównawczego, chociaż oba są bardzo prostymi rozwiązaniami.Nie sprawdziłem matematyki, ale podejrzewam, że obliczanie
Σ(n^2)
w tym samym przebiegu, w którym wykonujemy obliczeniaΣ(n)
, dostarczyłoby wystarczających informacji, aby uzyskać dwie brakujące liczby, ZróbΣ(n^3)
również, jeśli są trzy, i tak dalej.źródło
Problem z rozwiązaniami opartymi na sumach liczb polega na tym, że nie biorą one pod uwagę kosztów przechowywania i pracy z liczbami z dużymi wykładnikami ... w praktyce, aby działało to dla bardzo dużych liczb n, używana byłaby biblioteka dużych liczb . Możemy analizować wykorzystanie przestrzeni dla tych algorytmów.
Możemy analizować złożoność czasu i przestrzeni algorytmów sdcvvc i Dimitrisa Andreou.
Przechowywanie:
Więc
l_j \in \Theta(j log n)
Wykorzystane miejsce do przechowywania:
\sum_{j=1}^k l_j \in \Theta(k^2 log n)
Wykorzystane miejsce: przy założeniu, że obliczenia
a^j
wymagająceil(log_2 j)
czasu, całkowity czas:Całkowity wykorzystany czas:
\Theta(kn log n)
Jeśli ten czas i przestrzeń są wystarczające, możesz użyć prostego algorytmu rekurencyjnego. Niech b! I będzie i-tym wpisem w torbie, n liczbą liczb przed przeprowadzkami, a k liczbą przeprowadzek. W składni Haskell ...
Wykorzystana pamięć:
O(k)
dla listy,O(log(n))
dla stosu:O(k + log(n))
ten algorytm jest bardziej intuicyjny, ma tę samą złożoność czasu i zajmuje mniej miejsca.źródło
isInRange
to O (log n) , a nie O (1) : porównuje liczby z zakresu 1..n, więc musi porównać bity O (log n) . Nie wiem, w jakim stopniu ten błąd wpływa na resztę analizy.Poczekaj minutę. Jak podano pytanie, w torbie znajduje się 100 liczb. Bez względu na to, jak duże jest k, problem można rozwiązać w stałym czasie, ponieważ można użyć zestawu i usunąć liczby z zestawu co najwyżej 100-k iteracji pętli. 100 jest stałe. Zestaw pozostałych liczb jest twoją odpowiedzią.
Jeśli uogólnimy rozwiązanie liczb od 1 do N, nic się nie zmienia, z wyjątkiem tego, że N nie jest stałą, więc mamy czas O (N - k) = O (N). Na przykład, jeśli użyjemy zestawu bitów, ustawiamy bity na 1 w czasie O (N), iterujemy liczby, ustawiając bity na 0 w miarę upływu czasu (O (Nk) = O (N)), a następnie mam odpowiedź.
Wydaje mi się, że ankieter pytał cię, jak wydrukować zawartość końcowego zestawu w czasie O (k) zamiast w czasie O (N). Oczywiście, po ustawieniu nieco bitów, musisz iterować przez wszystkie N bitów, aby ustalić, czy powinieneś wydrukować numer, czy nie. Jeśli jednak zmienisz sposób implementacji zestawu, możesz wydrukować liczby w iteracjach. Odbywa się to poprzez umieszczenie liczb w obiekcie, który ma być przechowywany zarówno w zestawie skrótów, jak i podwójnie połączonej liście. Kiedy usuwasz obiekt z zestawu skrótów, usuwasz go również z listy. Odpowiedzi pozostaną na liście, która ma teraz długość k.
źródło
Aby rozwiązać pytanie o brakujące liczby 2 (i 3), możesz zmodyfikować
quickselect
, który średnio działaO(n)
i używa stałej pamięci, jeśli partycjonowanie jest wykonywane w miejscu.Podziel zestaw względem losowego elementu przestawnego
p
na partycjel
, które zawierają liczby mniejsze niż element przestawny ir
które zawierają liczby większe niż element przestawny.Określ, w których partycjach znajdują się 2 brakujące liczby, porównując wartość przestawną z rozmiarem każdej partycji (
p - 1 - count(l) = count of missing numbers in l
in - count(r) - p = count of missing numbers in r
)a) Jeśli w każdej partycji brakuje jednego numeru, użyj metody różnicy sum, aby znaleźć każdą brakującą liczbę.
(1 + 2 + ... + (p-1)) - sum(l) = missing #1
i((p+1) + (p+2) ... + n) - sum(r) = missing #2
b) Jeśli partycja jest brak zarówno liczby i partycja jest pusty, wówczas brakujące wartości to zarówno
(p-1,p-2)
lub(p+1,p+2)
w zależności od podziału brakuje liczby.Jeśli w jednej partycji brakuje 2 liczb, ale nie jest pusta, wróć do tej partycji.
Przy tylko 2 brakujących liczbach ten algorytm zawsze odrzuca co najmniej jedną partycję, więc zachowuje
O(n)
średni czas złożoności szybkiego wyboru. Podobnie, przy 3 brakujących liczbach, algorytm ten odrzuca również co najmniej jedną partycję z każdym przejściem (ponieważ jak w przypadku 2 brakujących liczb, co najwyżej tylko 1 partycja będzie zawierać wiele brakujących liczb). Nie jestem jednak pewien, o ile wydajność spada, gdy dodaje się więcej brakujących liczb.Oto implementacja, która nie korzysta z partycjonowania w miejscu, więc ten przykład nie spełnia wymagań dotyczących miejsca, ale ilustruje kroki algorytmu:
Próbny
źródło
Oto rozwiązanie, które wykorzystuje k bitów dodatkowej pamięci, bez żadnych sprytnych sztuczek i po prostu proste. Czas wykonania O (n), dodatkowa spacja O (k). Aby udowodnić, że można to rozwiązać bez uprzedniego zapoznania się z rozwiązaniem lub bez geniuszu:
źródło
(data [n - 1 - odd] % 2 == 1) ++odd;
?Czy możesz sprawdzić, czy każdy numer istnieje? Jeśli tak, możesz spróbować:
jeśli brakujące liczby to,
x
ay
następnie:Sprawdzasz więc zakres od
1
domax(x)
i znajdujesz numerźródło
max(x)
oznacza, kiedyx
jest liczbą?Być może ten algorytm może działać dla pytania 1:
Lub nawet lepiej:
Algorytm ten można w rzeczywistości rozszerzyć o dwie brakujące liczby. Pierwszy krok pozostaje taki sam. Kiedy wywołamy GetValue z dwoma brakującymi numerami, wynikiem będzie a
a1^a2
to dwie brakujące liczby. Powiedzmyval = a1^a2
Teraz, aby przesiać a1 i a2 z val, bierzemy dowolny ustawiony bit w val. Powiedzmy, że
ith
bit jest ustawiony na val. Oznacza to, że a1 i a2 mają różną parzystość with
pozycji bitu. Teraz wykonujemy kolejną iterację oryginalnej tablicy i zachowujemy dwie wartości xor. Jeden dla liczb z ustawionym i-tym bitem i drugi, który nie ma ustawionego i-tego bitu. Mamy teraz dwa segmenty liczb i ich gwarancje, którea1 and a2
będą znajdować się w różnych segmentach. Teraz powtórz to samo, co zrobiliśmy, aby znaleźć jeden brakujący element na każdym wiadrze.źródło
k=1
, prawda? Ale lubię używaćxor
sum, wydaje się to nieco szybsze.Możesz rozwiązać Q2, jeśli masz sumę obu list i iloczyn obu list.
(l1 to oryginał, l2 to zmodyfikowana lista)
Możemy to zoptymalizować, ponieważ suma szeregu arytmetycznego jest n razy średnia z pierwszego i ostatniego wyrażenia:
Teraz wiemy, że (jeśli a i b to usunięte liczby):
Możemy więc zmienić układ:
I pomnóż:
I przegrupuj, aby prawa strona miała zero:
Następnie możemy rozwiązać formułę kwadratową:
Przykładowy kod Python 3:
Nie znam złożoności funkcji sqrt, zmniejszania i sumowania, więc nie mogę obliczyć złożoności tego rozwiązania (jeśli ktoś wie, proszę o komentarz poniżej).
źródło
x1*x2*x3*...
?W przypadku Q2 jest to rozwiązanie, które jest nieco bardziej nieefektywne niż inne, ale nadal ma środowisko wykonawcze O (N) i zajmuje miejsce O (k).
Chodzi o to, aby uruchomić oryginalny algorytm dwa razy. W pierwszym otrzymujesz brakującą liczbę całkowitą, co daje górną granicę brakujących liczb. Zadzwońmy na ten numer
N
. Wiesz, że brakujące dwie liczby będą sumować sięN
, więc pierwsza liczba może być w przedziale,[1, floor((N-1)/2)]
podczas gdy druga będzie[floor(N/2)+1,N-1]
.W ten sposób ponownie zapętlasz wszystkie liczby, odrzucając wszystkie liczby, które nie są uwzględnione w pierwszym przedziale. Tymi, którzy śledzą ich sumę. Wreszcie poznasz jedną z brakujących dwóch liczb, a przez to drugą.
Mam wrażenie, że tę metodę można uogólnić i być może wielokrotne wyszukiwania przebiegają równolegle podczas jednego przejścia nad danymi wejściowymi, ale nie wiem, jak to zrobić.
źródło
Myślę, że można tego dokonać bez skomplikowanych równań i teorii matematycznych. Poniżej znajduje się propozycja rozwiązania złożoności czasowej i czasowej O (2n):
Założenia formularza wejściowego:
Liczba liczb w torbie = n
Liczba brakujących liczb = k
Liczby w torbie są reprezentowane przez tablicę długości n
Długość tablicy wejściowej dla algo = n
Brakujące wpisy w tablicy (liczby wyjęte z torby) są zastępowane wartością pierwszego elementu w tablicy.
Na przykład. Początkowo torba wygląda jak [2,9,3,7,8,6,4,5,1,10]. Jeśli 4 zostanie wyjęte, wartość 4 stanie się 2 (pierwszy element tablicy). Dlatego po wyjęciu 4 torba będzie wyglądać następująco [2,9,3,7,8,6,2,5,1,10]
Kluczem do tego rozwiązania jest oznaczenie INDEKSU odwiedzanej liczby poprzez zanegowanie wartości w tym INDEKSIE podczas przemierzania tablicy.
źródło
Istnieje ogólny sposób na uogólnienie takich algorytmów przesyłania strumieniowego. Chodzi o to, aby zastosować odrobinę randomizacji, aby, mam nadzieję, „rozłożyć”
k
elementy na niezależne problemy podrzędne, w których nasz oryginalny algorytm rozwiązuje problem za nas. Technikę tę stosuje się między innymi w rzadkiej rekonstrukcji sygnału.a
o rozmiarzeu = k^2
.h : {1,...,n} -> {1,...,u}
. (Jak wielokrotne zmiany )i
z1, ..., n
wzrostema[h(i)] += i
x
w strumieniu wejściowym zmniejsza[h(x)] -= x
.Jeśli wszystkie brakujące liczby zostały połączone w różne segmenty, niezerowe elementy tablicy będą teraz zawierać brakujące liczby.
Prawdopodobieństwo wysłania określonej pary do tego samego segmentu jest mniejsze niż
1/u
z definicji uniwersalnej funkcji skrótu. Ponieważ istniejąk^2/2
pary, mamy prawdopodobieństwo, że błąd jest co najwyżejk^2/2/u=1/2
. Oznacza to, że odniesiemy sukces z prawdopodobieństwem co najmniej 50%, a jeśli zwiększymyu
, zwiększymy nasze szanse.Zauważ, że ten algorytm zajmuje
k^2 logn
bity przestrzeni (Potrzebujemylogn
bitów na segment łyżki.) Odpowiada to przestrzeni wymaganej przez odpowiedź @Dimitris Andreou (W szczególności wymaganie miejsca faktoryzacji wielomianowej, które zdarza się również losowo.) Ten algorytm ma również stałą czas na aktualizację, a nie czask
w przypadku sum mocy.W rzeczywistości możemy być jeszcze bardziej wydajni niż metoda sumy mocy, stosując sztuczkę opisaną w komentarzach.
źródło
xor
w każdym segmencie, a niesum
, jeśli jest to szybsze na naszej maszynie.k <= sqrt(n)
- przynajmniej jeśliu=k^2
? Załóżmy, że k = 11 i n = 100, wtedy miałbyś 121 segmentów, a algorytm byłby podobny do tablicy 100 bitów, którą sprawdzasz podczas odczytywania każdego # ze strumienia. Zwiększenieu
zwiększa szanse na sukces, ale istnieje limit, o ile możesz go zwiększyć, zanim przekroczysz ograniczenie przestrzeni.n
znacznie większym stopniu niżk
, jak sądzę, ale w rzeczywistości można uzyskać miejscek logn
za pomocą metody bardzo podobnej do opisanej funkcji mieszania, przy jednoczesnym ciągłym aktualizowaniu czasu. Jest to opisane w gnunet.org/eppstein-set-reconciliation , podobnie jak metoda sumy mocy, ale w zasadzie masz skrót do „dwóch z k” segmentów z silną funkcją skrótu, taką jak haszowanie tabelaryczne, co gwarantuje, że niektóre segmenty będą miały tylko jeden element . Aby zdekodować, identyfikujesz to wiadro i usuwa element z obu jego wiader, co (prawdopodobnie) uwalnia kolejne wiadro i tak dalejBardzo proste rozwiązanie Q2, które, jestem zaskoczony, nikt jeszcze nie odpowiedział. Użyj metody z Q1, aby znaleźć sumę dwóch brakujących liczb. Oznaczmy to jako S, wtedy jedna z brakujących liczb jest mniejsza niż S / 2, a druga większa niż S / 2 (duh). Zsumuj wszystkie liczby od 1 do S / 2 i porównaj je z wynikiem formuły (podobnie do metody z Q1), aby znaleźć niższą wartość między brakującymi liczbami. Odejmij go od S, aby znaleźć większą brakującą liczbę.
źródło
Bardzo fajny problem. Postanowiłbym użyć różnicy ustawionej dla Qk. Wiele języków programowania ma nawet obsługę tego, na przykład w Ruby:
Prawdopodobnie nie jest to najbardziej wydajne rozwiązanie, ale skorzystałbym z niego w prawdziwym życiu, gdyby w tym przypadku stanęło mi przed takim zadaniem (znane granice, niskie granice). Jeśli zestaw liczb byłby bardzo duży, rozważałbym oczywiście bardziej wydajny algorytm, ale do tego czasu proste rozwiązanie byłoby dla mnie wystarczające.
źródło
Możesz spróbować użyć filtra Bloom . Wstaw każdą liczbę do torby w rozkwicie, a następnie powtarzaj cały zestaw 1-k, aż zgłosisz, że nie znaleziono żadnej z nich. To może nie znaleźć odpowiedzi we wszystkich scenariuszach, ale może być wystarczająco dobrym rozwiązaniem.
źródło
Przyjąłem inne podejście do tego pytania i zbadałem ankietera, aby uzyskać więcej informacji na temat większego problemu, który próbuje rozwiązać. W zależności od problemu i otaczających go wymagań, oczywiste rozwiązanie oparte na zestawie może być właściwe, a podejście polegające na generowaniu listy i wybieraniu go później może nie być właściwe.
Na przykład może się zdarzyć, że osoba przeprowadzająca wywiad wyśle
n
wiadomości i musi wiedzieć,k
które nie przyniosły odpowiedzi, i musi wiedzieć o tym w jak najkrótszym czasie naściennym po otrzymaniun-k
odpowiedzi. Powiedzmy również, że natura kanału komunikatów jest taka, że nawet przy pełnym otwarciu jest wystarczająco dużo czasu na wykonanie przetwarzania między komunikatami bez wpływu na to, ile czasu zajmuje uzyskanie wyniku końcowego po otrzymaniu ostatniej odpowiedzi. Czas ten może zostać wykorzystany na wstawienie jakiegoś identyfikującego elementu każdej wysłanej wiadomości do zestawu i usunięcie go wraz z nadejściem każdej odpowiadającej odpowiedzi. Po otrzymaniu ostatniej odpowiedzi jedyne, co należy zrobić, to usunąć jego identyfikator z zestawu, co w typowych implementacjach wymagaO(log k+1)
. Następnie zestaw zawiera listęk
brakujących elementów i nie trzeba wykonywać żadnego dodatkowego przetwarzania.To z pewnością nie jest najszybsze podejście do przetwarzania partii wstępnie wygenerowanych pakietów liczb, ponieważ wszystko działa
O((log 1 + log 2 + ... + log n) + (log n + log n-1 + ... + log k))
. Ale działa dla dowolnej wartościk
(nawet jeśli nie jest znana z góry), aw powyższym przykładzie został zastosowany w sposób, który minimalizuje najbardziej krytyczny interwał.źródło
Możesz motywować rozwiązanie, myśląc o nim w kategoriach symetrii (grupy, w języku matematyki). Bez względu na kolejność zbioru liczb odpowiedź powinna być taka sama. Jeśli zamierzasz użyć
k
funkcji do ustalenia brakujących elementów, powinieneś pomyśleć o tym, jakie funkcje mają tę właściwość: symetryczną. Funkcjas_1(x) = x_1 + x_2 + ... + x_n
jest przykładem funkcji symetrycznej, ale są też inne o wyższym stopniu. W szczególności rozważ podstawowe elementarne funkcje symetryczne . Elementarną funkcją symetryczną stopnia 2 jests_2(x) = x_1 x_2 + x_1 x_3 + ... + x_1 x_n + x_2 x_3 + ... + x_(n-1) x_n
suma wszystkich iloczynów dwóch elementów. Podobnie dla elementarnych funkcji symetrycznych stopnia 3 i wyższych. Są oczywiście symetryczne. Co więcej, okazuje się, że są one elementami składowymi wszystkich funkcji symetrycznych.Zauważ, że możesz budować podstawowe funkcje symetryczne
s_2(x,x_(n+1)) = s_2(x) + s_1(x)(x_(n+1))
. Dalsze przemyślenia powinny cię przekonaćs_3(x,x_(n+1)) = s_3(x) + s_2(x)(x_(n+1))
i tak dalej, aby można je było obliczyć za jednym razem.Jak rozpoznać, których elementów brakowało w tablicy? Pomyśl o wielomianu
(z-x_1)(z-x_2)...(z-x_n)
. Ocenia,0
czy wpiszesz którąś z liczbx_i
. Rozwijając wielomian, otrzymujeszz^n-s_1(x)z^(n-1)+ ... + (-1)^n s_n
. Pojawiają się tutaj również elementarne funkcje symetryczne, co naprawdę nie jest zaskoczeniem, ponieważ wielomian powinien pozostać taki sam, jeśli zastosujemy jakąkolwiek permutację do pierwiastków.Możemy więc zbudować wielomian i spróbować go uwzględnić, aby dowiedzieć się, które liczby nie są w zbiorze, jak wspomnieli inni.
W końcu, jeśli mamy do czynienia o przepełnieniu pamięci z dużą liczbą (n-ty wielomian symetryczny będzie rzędu
100!
), możemy wykonać te obliczeniamod p
, gdziep
jest głównym większy niż 100. W tym przypadku mamy do oceny wielomianmod p
i uważają, że ponownie ocenia do0
kiedy wejście jest liczbą w zestawie i zwraca wartość niezerową, gdy wejście jest liczbą nie w zestawie. Jednak, jak zauważyli inni, aby uzyskać wartości z wielomianu w czasie, który zależy odk
, nieN
musimy uwzględniać wielomianumod p
.źródło
Jeszcze innym sposobem jest użycie filtrowania wykresu szczątkowego.
Załóżmy, że mamy cyfry od 1 do 4 i brakuje 3. Reprezentacja binarna jest następująca:
1 = 001b, 2 = 010b, 3 = 011b, 4 = 100b
I mogę utworzyć schemat blokowy jak poniżej.
Zauważ, że wykres przepływu zawiera x węzłów, zaś x oznacza liczbę bitów. A maksymalna liczba krawędzi to (2 * x) -2.
Tak więc dla 32-bitowej liczby całkowitej zajmie miejsce O (32) lub miejsce O (1).
Teraz, jeśli usunę pojemność dla każdej liczby, zaczynając od 1,2,4, pozostanie mi wykres resztkowy.
W końcu uruchomię pętlę taką jak poniżej,
Teraz wynik
result
zawiera liczby, których również nie brakuje (fałszywie dodatnie). Ale k <= (rozmiar wyniku) <= n, gdyk
brakuje elementów.Przejdę podaną listę po raz ostatni, aby zaznaczyć brakujący wynik lub nie.
Złożoność czasowa będzie więc O (n).
Wreszcie możliwe jest zmniejszenie liczby fałszywie dodatnich (i wymaganej przestrzeni) poprzez węzły
00
,01
,11
,10
zamiast po prostu0
i1
.źródło
Prawdopodobnie potrzebujesz wyjaśnienia, co oznacza O (k).
Oto banalne rozwiązanie dla dowolnego k: dla każdego v w zbiorze liczb kumuluj sumę 2 ^ v. Na końcu pętla i od 1 do N. Jeśli suma bitowa AND z 2 ^ i wynosi zero, to brakuje i. (Lub liczbowo, jeśli dolna część sumy podzielonej przez 2 ^ i jest parzysta. Or
sum modulo 2^(i+1)) < 2^i
.)Łatwe, prawda? Czas O (N), pamięć O (1) i obsługuje dowolne k.
Tyle że obliczasz ogromne liczby, które na prawdziwym komputerze wymagałyby przestrzeni O (N). W rzeczywistości to rozwiązanie jest identyczne z wektorem bitowym.
Więc możesz być sprytny i obliczyć sumę i sumę kwadratów i sumę kostek ... aż do sumy v ^ k, i wykonać fantazyjną matematykę, aby uzyskać wynik. Ale to także duże liczby, co nasuwa pytanie: o jakim abstrakcyjnym modelu działania mówimy? Ile wpasowuje się w przestrzeń O (1) i ile czasu zajmuje zsumowanie liczb dowolnej wielkości?
źródło
Oto rozwiązanie, które nie opiera się na złożonej matematyce, jak robią to odpowiedzi sdcvvc / Dimitris Andreou, nie zmienia tablicy wejściowej tak, jak zrobili to caf i pułkownik Panic, i nie używa zestawu bitów o ogromnych rozmiarach, jak Chris Lercher, JeremyP i wiele innych zrobiło. Zasadniczo zacząłem od pomysłu Svalorzen / Gilada Deutcha na drugi kwartał, uogólniłem go na wspólny przypadek Qk i zaimplementowałem w Javie, aby udowodnić, że algorytm działa.
Pomysł
Załóżmy, że mamy dowolny przedział I, o którym wiemy tylko, że zawiera on co najmniej jedną z brakujących liczb. Po jednym przejściu przez tablicę wejściowego, patrząc tylko na numerach od I , można otrzymać zarówno sumy S , a ilość Q brakujących numerów z I . Robimy to po prostu zmniejszając długość I za każdym razem, gdy napotykamy liczbę od I (dla uzyskania Q ) i zmniejszając z góry obliczoną sumę wszystkich liczb w I o tę napotkaną liczbę za każdym razem (dla uzyskania S ).
Teraz patrzymy na S i Q . Jeśli Q = 1 , oznacza to, że wówczas , że zawiera tylko jeden z brakujących numerów, a ta liczba jest wyraźnie S . Oznaczamy I jako ukończony (w programie nazywa się to „jednoznacznym”) i nie uwzględniamy go w dalszych rozważaniach. Z drugiej strony, gdy Q> 1 , można obliczyć średnią A = S / P brakujących numerów zawartych w I . Ponieważ wszystkie liczby są różne, co najmniej jeden z tych numerów jest mniejsze niż A i co najmniej jeden jest większy od A . Teraz podzieliliśmy I na A.na dwa mniejsze przedziały, z których każdy zawiera co najmniej jedną brakującą liczbę. Zauważ, że nie ma znaczenia, do których przedziałów przypisujemy A, jeśli jest to liczba całkowita.
Wykonujemy kolejny przebieg tablicowy, obliczając S i Q osobno dla każdego z przedziałów (ale w tym samym przebiegu), a następnie zaznaczamy przedziały dla Q = 1 i przedziały podziału dla Q> 1 . Kontynuujemy ten proces, dopóki nie pojawią się nowe „dwuznaczne” przedziały, tzn. Nie mamy nic do podzielenia, ponieważ każdy przedział zawiera dokładnie jedną brakującą liczbę (i zawsze znamy tę liczbę, ponieważ znamy S ). Zaczynamy od jedynego przedziału „całego zakresu” zawierającego wszystkie możliwe liczby (np. [1..N] w pytaniu).
Analiza złożoności czasu i przestrzeni
Całkowita liczba przejść p, które musimy wykonać, aż do zatrzymania procesu, nigdy nie jest większa niż liczba brakujących liczb k . Nierówność p <= k można udowodnić rygorystycznie. Z drugiej strony istnieje również empiryczna górna granica p <log 2 N + 3, która jest przydatna dla dużych wartości k . Musimy przeprowadzić wyszukiwanie binarne dla każdej liczby tablicy wejściowej, aby określić przedział, do którego ona należy. Dodaje to mnożnik log k do złożoności czasu.
W sumie złożoność czasu wynosi O (N ᛫ min (k, log N) ᛫ log k) . Zauważ, że w przypadku dużego k jest to znacznie lepsze niż w przypadku metody sdcvvc / Dimitris Andreou, czyli O (N ᛫ k) .
Do swojej pracy algorytm wymaga O (k) dodatkowej przestrzeni do przechowywania w większości k przedziałów, która jest znacznie lepsza niż O (N) w rozwiązaniach „zestawu bitów”.
Implementacja Java
Oto klasa Java, która implementuje powyższy algorytm. Zawsze zwraca posortowaną tablicę brakujących liczb. Poza tym nie wymaga liczenia brakujących liczb k, ponieważ oblicza go w pierwszym przejściu. Cały zakres liczb jest podany przez parametry
minNumber
imaxNumber
(np. 1 i 100 dla pierwszego przykładu w pytaniu).Dla uczciwości klasa ta otrzymuje dane wejściowe w postaci
NumberBag
obiektów.NumberBag
nie zezwala na modyfikację tablicy i losowy dostęp, a także liczy, ile razy tablica była wymagana do sekwencyjnego przechodzenia. Jest również bardziej odpowiedni do testowania dużych tablic niżIterable<Integer>
dlatego, że pozwala uniknąćint
pakowania pierwotnych wartości i pozwala na zawijanie części dużegoint[]
dla wygodnego przygotowania testu. To nie jest trudne do zastąpienia, w razie potrzeby,NumberBag
przezint[]
lubIterable<Integer>
wpisaćfind
podpis, zmieniając dwie pętle dla w nim na te foreach.Testy
Proste przykłady demonstrujące użycie tych klas podano poniżej.
Testowanie dużej tablicy można wykonać w ten sposób:
Wypróbuj je na Ideone
źródło
Uważam, że mam algorytm
O(k)
czasu iO(log(k))
przestrzeni, biorąc pod uwagę, że masz dostępne funkcjefloor(x)
ilog2(x)
dla dowolnie dużych liczb całkowitych:Masz
k
-bitową liczbę całkowitą (stądlog8(k)
spację), do której dodajeszx^2
, gdzie x to kolejny numer znaleziony w torbie:s=1^2+2^2+...
Zajmuje to trochęO(N)
czasu (co nie jest problemem dla ankietera). Na koniec dostajeszj=floor(log2(s))
największą liczbę, której szukasz. Następnies=s-j
i ponownie wykonaj powyższe czynności:Teraz zwykle nie masz funkcji floor i log2 dla
2756
liczb całkowitych -bit, ale zamiast podwójnych. Więc? Po prostu dla każdego 2 bajtów (lub 1, 3, lub 4) możesz użyć tych funkcji, aby uzyskać pożądane liczby, ale to dodajeO(N)
czynnik do złożoności czasuźródło
Może to zabrzmieć głupio, ale w pierwszym przedstawionym problemie musiałbyś zobaczyć wszystkie pozostałe liczby w torbie, aby je właściwie dodać, aby znaleźć brakującą liczbę za pomocą tego równania.
Ponieważ widzisz wszystkie liczby, po prostu znajdź brakującą liczbę. To samo dotyczy braku dwóch liczb. Myślę, że całkiem proste. Nie ma sensu używać równania, gdy zobaczysz liczby pozostające w torbie.
źródło
Myślę, że można to uogólnić w następujący sposób:
Oznacz S, M jako wartości początkowe dla sumy szeregów arytmetycznych i mnożenia.
Powinienem pomyśleć o formule do obliczenia tego, ale nie o to chodzi. W każdym razie, jeśli brakuje jednego numeru, już podałeś rozwiązanie. Jeśli jednak brakuje dwóch liczb, oznaczmy nową sumę i całkowitą wielokrotność przez S1 i M1, które będą następujące:
Ponieważ znasz S1, M1, M i S, powyższe równanie można rozwiązać, aby znaleźć aib, brakujące liczby.
Teraz brakuje trzech liczb:
Teraz twoje nieznane wynosi 3, podczas gdy masz tylko dwa równania, z których możesz rozwiązać.
źródło
M1 = M / (a * b)
(zobacz tę odpowiedź ). To działa dobrze.Nie wiem, czy to jest skuteczne, czy nie, ale chciałbym zaproponować to rozwiązanie.
4. Uzyskaj sumę brakujących numerów przy zwykłym podejściu do suma formuły diff i powiedzmy, że diff to d.
Teraz uruchom pętlę, aby uzyskać możliwe pary (p, q), z których obie leżą w [1, 100] i sumują do d.
Po uzyskaniu pary sprawdź, czy (wynik 3) XOR p = q i jeśli tak, to koniec.
Popraw mnie, jeśli się mylę, a także skomentuj złożoność czasu, jeśli jest to poprawne
źródło
Możemy wykonać Q1 i Q2 w O (log n) przez większość czasu.
Załóżmy, że nasza
memory chip
składa się z tablicyn
liczbtest tubes
. A liczbax
w probówce jest reprezentowana przezx
milliliter
ciecz chemiczną.Załóżmy, że nasz procesor to
laser light
. Kiedy zapalamy laser, przechodzi on przez wszystkie rury prostopadle do jego długości. Za każdym razem, gdy przechodzi przez ciecz chemiczną, jasność jest zmniejszana o1
. Przekazywanie światła przy pewnym znaku mililitra jest działaniemO(1)
.Teraz, jeśli zapalimy nasz laser na środku probówki i uzyskamy moc świetlną
n/2
.n/2
. Możemy również sprawdzić, czy jasność jest zmniejszona o1
lub2
. jeśli zostanie zmniejszona do1
tego czasu, jedna brakująca liczba jest mniejsza niż,n/2
a inna jest większa niżn/2
. Jeśli zmniejszy się o2
to, obie liczby są mniejsze niżn/2
.Możemy powtórzyć powyższy proces wielokrotnie i zawężając naszą domenę problemów. Na każdym etapie zmniejszamy domenę o połowę. I wreszcie możemy dojść do naszego wyniku.
Równoległe algorytmy, o których warto wspomnieć (ponieważ są interesujące),
O(log^3 n)
czasie. A następnie brakującą liczbę można znaleźć na podstawie wyszukiwania binarnego wO(log n)
czasie.n
procesory, to każdy proces może sprawdzić jedno z danych wejściowych i ustawić flagę identyfikującą liczbę (dogodnie w tablicy). I w następnym kroku każdy proces może sprawdzić każdą flagę i ostatecznie wypisać liczbę, która nie jest oflagowana. Cały proces zajmie trochęO(1)
czasu. Ma dodatkoweO(n)
zapotrzebowanie na miejsce / pamięć.Należy zauważyć, że dwa równoległe algorytmy przedstawione powyżej mogą wymagać dodatkowej przestrzeni, jak wspomniano w komentarzu .
źródło
O(logn)
na komputerze.N
odO(N)
czasu i więcej niż czasu (pod względem jego zależnościN
), od którego zamierzamy zrobić lepiej niż.