Ciekawy problem
02 Mar 2015Ostatnio miałem okazję rozwiązywać zadania algorytmiczne na portalu Codility (któremu będzie dedykowany jeszcze następny wpis). Tego typu zadania może nie zawsze są najlepsze, jeśli chodzi o rekrutację, ale świetnie się nadają do treningu umysłu. Kiedy uda się rozwiązać trudny problem, daje to dużo satysfakcji. Tutaj opisuję jeden z takich problemów.
- Mamy niemalejący ciąg nieujemnych liczb rzeczywistych (w tablicy
A
, indeksowanej od 0). - Mamy dwa indeksy,
p
iq
, takie że0 <= p < q < A.length
. - Policz, ile jest takich par
p
iq
, żeA[p] * A[q] <= A[p] + A[q]
. - Złożoność czasowa rozwiązania powinna wynieść maksymalnie
O(N)
, a pamięciowaO(1)
, gdzieN
oznacza liczbę elementów ciągu (rozmiar tablicy).
W rzeczywistości należy wziąć pod uwagę jeszcze to, że liczby rzeczywiste są zaokrąglane, np. 10000000000.0 == 10000000001.0
.
Ten problem jednak tu pominę.
Zanim jednak przeczytasz, jak rozwiązać ten problem, zachęcam Cię, żeby samodzielnie się z nim zmierzyć. Jeśli tylko masz chwilę czasu, wyjmij notatnik, otwórz swoje ulubione IDE i spróbuj rozwiązać ten problem. Użyteczny może być też arkusz kalkulacyjny, aby policzyć, kiedy faktycznie dany przypadek zachodzi.
Jeśli chodzi o sam kod, możesz skorzystać z poniższego szablonu:
W powyższym przypadku program powinien wyświetlić 8
.
Rozwiązanie
Poważnym ograniczeniem dla rozwiązania jest złożoność.
O(N)
dla złożoności czasowej oznacza, że w praktyce nie możemy zagnieżdżać pętli.
Możemy skorzystać najwyżej z kilku pętli niezagnieżdżonych, w których liczba iteracji zależy liniowo od N
.
Złożoność pamięci O(1)
oznacza, że nie powinniśmy tworzyć żadnych struktur danych, których rozmiar zależy od N
.
Innymi słowy, nawet nie myśl o brute force i zliczaniu elementów w wygenerowanej dwuwymiarowej tablicy.
Przykład będzie w Javie, więc musimy też unikać rekurencji. (Na przykład w Scali naturalniej przyszło by mi użycie rekurencji, jednak z pewnych względów musimy sobie radzić z pętlami).
Na początek proponuję przyjrzeć się warunkowi, który dwie liczby muszą spełniać.
Zgodnie z zadaniem, musimy policzyć takie pary indeksów p
i q
, że A[p] * A[q] <= A[p] + A[q]
.
Innymi słowy, musimy wyznaczyć takie pary liczb P
i Q
, że P * Q <= P + Q
.
W takiej sytuacji warto skorzystać z WolframAlpha, albo od razu rozpisać sobie wyniki w tabelce dla przykładowych danych.
Poniżej zamieściłem taką taką tabelkę, gdzie T oznacza, że warunek A[p] * A[q] <= A[p] + A[q]
jest spełniony, a N, że nie jest spełniony.
q | 0 | 1 | 2 | 3 | 4 | 5 | ||
---|---|---|---|---|---|---|---|---|
A[q] | 0.5 | 1.5 | 2.0 | 2.0 | 3.0 | 9.3 | ||
p | A[p] | |||||||
0 | 0.5 | N | N | N | N | N | ||
1 | 1.5 | N | N | T | T | |||
2 | 2.0 | T | T | T | ||||
3 | 2.0 | T | T | |||||
4 | 3.0 | T |
Warunek będzie spełniony dla wartości maksymalnie na dole po prawej, dla odpowiednio dużych wartości A[p]
i A[q]
.
Ponieważ tablica posortowana jest niemalejąco, warunek jest spełniony dla odpowiednio dużych indeksów p
i q
.
Jeśli nie będzie spełniony dla p = A.length - 2
i q = A.length - 1
(pamiętamy o tym, że p < q
), to nie będzie spełniony wcale.
Podejście, jakie wykorzystałem do rozwiązania tego problemu, było następujące:
- Ustawiam początkowe wartości
count = 0
,p = 0
,q = A.length - 1
. -
Jeśli
p < q
, sprawdzam, czy zachodzi warunekA[p] * A[q] >= A[p] + A[q]
.a) Jeśli ten warunek zachodzi, dodaję do
count
liczbę wartości, które spełniają warunek, czyliq - p
, następnie zmniejszamq
o jeden i przechodzę do punktu 2.b) Jeśli ten warunek nie zachodzi, zwiększam
p
o jeden i przechodzę do punktu 2. - Zwracam wartość
count
.
Dlaczego to działa? Zobacz na przykładzie, który pokazałem już wcześniej.
Na początku ustawiam wartości count = 0
, p = 0
, q = 5
.
Czyli tabela z danymi wygląda tak:
q | 0 | 1 | 2 | 3 | 4 | 5 | ||
---|---|---|---|---|---|---|---|---|
A[q] | 0.5 | 1.5 | 2.0 | 2.0 | 3.0 | 9.3 | ||
p | A[p] | |||||||
0 | 0.5 | N | N | N | N | N | ||
1 | 1.5 | N | N | T | T | |||
2 | 2.0 | T | T | T | ||||
3 | 2.0 | T | T | |||||
4 | 3.0 | T |
Ponieważ p = 0 < q = 5
, sprawdzam, czy 0.5 * 9.3 <= 0.5 + 9.3
.
Okazuje się, że nie jest to prawda, warunek nie zachodzi, więc na pewno dla p = 0
nie znajdę żadnego q
, które spełniałoby ten warunek.
Mogę tym samym pominąć cały wiersz dla p = 0
, więc zwiększam ‘p’ o jeden.
q | 0 | 1 | 2 | 3 | 4 | 5 | ||
---|---|---|---|---|---|---|---|---|
A[q] | 0.5 | 1.5 | 2.0 | 2.0 | 3.0 | 9.3 | ||
p | A[p] | |||||||
1 | 1.5 | N | N | T | T | |||
2 | 2.0 | T | T | T | ||||
3 | 2.0 | T | T | |||||
4 | 3.0 | T |
Ponieważ 1 < 5
, sprawdzam, czy 1.5 * 9.3 <= 1.5 + 9.3
.
Warunek zachodzi dla p = 1
, co oznacza, że dla wszystkich kolejnych p
, aż do A.length - 1
także będzie zachodzić.
Liczba odnalezionych par to q - p = 5 - 1 = 4
, dlatego zwiększam count
o 4
.
Mogę też nie brać już pod uwagę ostatniej kolumny, dlatego zmniejszam q
o jeden.
q | 0 | 1 | 2 | 3 | 4 | |||
---|---|---|---|---|---|---|---|---|
A[q] | 0.5 | 1.5 | 2.0 | 2.0 | 3.0 | |||
p | A[p] | |||||||
1 | 1.5 | N | N | T | ||||
2 | 2.0 | T | T | |||||
3 | 2.0 | T | ||||||
4 | 3.0 |
Ponieważ 1 < 4
, sprawdzam, czy 1.5 * 3.0 <= 1.5 + 3.0
.
Tutaj warunek też zachodzi, więc do count
dodaję jeszcze 4 - 1
i zmniejszam p
o jeden, aby nie uwzględniać już kolumny z indeksem 4
.
q | 0 | 1 | 2 | 3 | ||||
---|---|---|---|---|---|---|---|---|
A[q] | 0.5 | 1.5 | 2.0 | 2.0 | ||||
p | A[p] | |||||||
1 | 1.5 | N | N | |||||
2 | 2.0 | T | ||||||
3 | 2.0 | |||||||
4 | 3.0 |
Ponieważ 1 < 3
, sprawdzam, czy 1.5 * 2.0 <= 1.5 + 2.0
.
Tym razem, warumek nie zachodzi, większ zwiększam p
o jeden, aby pominąć wiersz z indeksem 1
.
q | 0 | 1 | 2 | 3 | ||||
---|---|---|---|---|---|---|---|---|
A[q] | 0.5 | 1.5 | 2.0 | 2.0 | ||||
p | A[p] | |||||||
2 | 2.0 | T | ||||||
3 | 2.0 | |||||||
4 | 3.0 |
Ponieważ 2 < 3
, sprawdzam, czy 2.0 * 2.0 <= 2.0 + 2.0
.
Warunek jest spełniony, więc jeszcze raz zmniejszam p
i aktualizuję count
.
Po tym kroku p
zrównało się z q
, zatem należy przerwać działanie algorytmu.
Ostatecznie policzone zostały wszystkie pary, spełniające warunek.
Otrzymano count = 4 + 3 + 1
, czyli 8
par.
Wynik jest prawidłowy, tylko że przykładowe dane nie uwzględniają jeszcze jednej rzeczy.
Uwzględnianie zer
Przypomnę jeszcze raz założenia: ciąg zawiera liczby rzeczywiste nieujemne i jest posortowany niemalejąco.
Innymi słowy, na początku tablicy może znaleźć się jedno lub więcej zer, więc być może trzeba dodać jeszcze pary zer, które oczywiście spełniają warunek (0 * 0 >= 0 + 0
).
Poniżej tabela, gdzie na początku ciągu dodałem trzy zera.
q | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | ||
---|---|---|---|---|---|---|---|---|---|---|---|
A[q] | 0.0 | 0.0 | 0.0 | 0.5 | 1.5 | 2.0 | 2.0 | 3.0 | 9.3 | ||
p | A[p] | ||||||||||
0 | 0.0 | T | T | N | N | N | N | N | N | ||
1 | 0.0 | T | N | N | N | N | N | N | |||
2 | 0.0 | N | N | N | N | N | N | ||||
3 | 0.5 | N | N | N | N | N | |||||
4 | 1.0 | N | N | T | T | ||||||
5 | 2.0 | T | T | T | |||||||
6 | 2.0 | T | T | ||||||||
7 | 3.0 | T |
Jak widać, zera zachowują się nieco inaczej i musimy je dodatkowo uwzględnić. Mianowicie, oprócz poprzednich par dla nie-zer, które znalazł algorytm, należy dodać wszystkie możliwe pary zer. Można to zrobić w następujący sposób:
Dla każdego indeksu i
z tablicy A
(począwszy od 1),
- jeśli
A[i] == 0
, dodaji
docount
i przejdź do kolejnego indeksu; - w przeciwnym wypadku przerwij wykonywanie.
Element z indeksem 0
nie jest istotny.
Jeśli element z indeksem 1
, będzie zerowy, to należy dodać jedną parę, ponieważ jedno zero pojawiło się wcześniej.
Jeśli element z indeksem 2
, będzie zerowy, to należy dodać jeszcze dwie pary, ponieważ wcześniej pojawiły się już dwa zera.
Jeśli element z indeksem k
, będzie zerowy, to należy dodać jeszcze k
par, ponieważ wcześniej pojawiło się już k
zer.
Jeśli natomiast element z indeksem k
nie będzie zerowy, to przerywamy zliczanie par zer, bo wiadomo, że żadne zero już już się nie pojawi.
Pełne rozwiązanie
Ostatecznie metoda count
w Javie może wyglądać tak:
Do poczytania
Zachęcam, żeby zajrzeć na podstronę Codility dla programistów, gdzie można znaleźć ten i wiele innych problemów algorytmicznych. Niecierpliwi mogą przejrzeć odpowiedzi, na przykład te, przygotowane przez pewnego studenta jednej z pekińskich uczelni.