Załóżmy, że mamy tablicę n liczb całkowitych reprezentujących ceny akcji w jednym dniu. Chcemy znaleźć parę (buyDay, sellDay) , gdzie buyDay ≤ sellDay , taką, że gdybyśmy kupili akcje w buyDay i sprzedali w sellDay , zmaksymalizowalibyśmy nasz zysk.
Oczywiście istnieje rozwiązanie algorytmu O (n 2 ) polegające na wypróbowaniu wszystkich możliwych par (buyDay, sellDay) i wyciągnięciu z nich wszystkiego, co najlepsze. Czy istnieje jednak lepszy algorytm, może taki, który działa w czasie O (n) ?
arrays
algorithm
big-o
time-complexity
Ajeet Ganga
źródło
źródło
Odpowiedzi:
Uwielbiam ten problem. To klasyczne pytanie do rozmowy kwalifikacyjnej iw zależności od tego, jak o tym myślisz, będziesz otrzymywać coraz lepsze rozwiązania. Z pewnością można to zrobić lepiej niż O (n 2 ), a tutaj wymieniłem trzy różne sposoby, w jakie możesz pomyśleć o problemie. Mam nadzieję, że to odpowiada na Twoje pytanie!
Po pierwsze, rozwiązanie typu „podziel i rządź”. Zobaczmy, czy możemy rozwiązać ten problem, dzieląc dane wejściowe na pół, rozwiązując problem w każdej podtablicy, a następnie łącząc oba razem. Okazuje się, że faktycznie możemy to zrobić i możemy to zrobić skutecznie! Intuicja jest następująca. Jeśli mamy tylko jeden dzień, najlepszą opcją jest zakup tego dnia, a następnie odsprzedanie go tego samego dnia bez zysku. W przeciwnym razie podziel tablicę na dwie połowy. Jeśli zastanawiamy się, jaka może być optymalna odpowiedź, to musi ona znajdować się w jednym z trzech miejsc:
Możemy uzyskać wartości (1) i (2), rekurencyjnie wywołując nasz algorytm w pierwszej i drugiej połowie. W przypadku opcji (3) sposobem na osiągnięcie największego zysku byłoby kupowanie w najniższym punkcie pierwszej połowy i sprzedaż w najlepszym momencie drugiej połowy. Możemy znaleźć wartości minimalne i maksymalne w dwóch połówkach, wykonując proste skanowanie liniowe na wejściu i znajdując dwie wartości. To daje nam algorytm z następującą powtarzalnością:
Używając Twierdzenia głównego do rozwiązania rekurencji, stwierdzamy, że działa to w czasie O (n lg n) i będzie używać przestrzeni O (lg n) dla wywołań rekurencyjnych. Właśnie pokonaliśmy naiwne rozwiązanie O (n 2 )!
Ale poczekaj! Możemy zrobić znacznie lepiej niż to. Zwróć uwagę, że jedynym powodem, dla którego mamy składnik O (n) w naszym powtórzeniu, jest to, że musieliśmy przeskanować całe dane wejściowe, próbując znaleźć minimalne i maksymalne wartości w każdej połowie. Ponieważ już rekurencyjnie badamy każdą połowę, być może możemy zrobić to lepiej, jeśli rekurencja również zwróci minimalne i maksymalne wartości przechowywane w każdej połowie! Innymi słowy, nasza rekurencja zwraca trzy rzeczy:
Te dwie ostatnie wartości można obliczyć rekurencyjnie przy użyciu prostej rekurencji, którą możemy uruchomić w tym samym czasie co rekurencję do obliczenia (1):
Jeśli użyjemy tego podejścia, nasza relacja powtarzania jest teraz
Użycie tutaj twierdzenia głównego daje nam czas wykonania O (n) z przestrzenią O (lg n), co jest nawet lepsze niż nasze oryginalne rozwiązanie!
Ale chwileczkę - możemy zrobić jeszcze lepiej! Pomyślmy o rozwiązaniu tego problemu za pomocą programowania dynamicznego. Chodzi o to, aby przemyśleć problem w następujący sposób. Załóżmy, że znaliśmy odpowiedź na problem po przyjrzeniu się pierwszym k elementom. Czy moglibyśmy wykorzystać naszą wiedzę o (k + 1) elemencie, w połączeniu z naszym początkowym rozwiązaniem, do rozwiązania problemu dla pierwszych (k + 1) elementów? Jeśli tak, możemy uzyskać świetny algorytm, rozwiązując problem dla pierwszego elementu, potem dla pierwszych dwóch, potem pierwszych trzech itd., Aż obliczymy go dla pierwszych n elementów.
Zastanówmy się, jak to zrobić. Jeśli mamy tylko jeden element, już wiemy, że musi to być najlepsza para kupna / sprzedaży. Teraz załóżmy, że znamy najlepszą odpowiedź dla pierwszych k elementów i spójrzmy na (k + 1) element. Zatem jedynym sposobem, w jaki ta wartość może stworzyć rozwiązanie lepsze niż to, które mieliśmy dla pierwszych k elementów, jest to, że różnica między najmniejszym z pierwszych k elementów a tym nowym elementem jest większa niż największa różnica, jaką do tej pory obliczyliśmy. Załóżmy więc, że przechodząc przez elementy, śledzimy dwie wartości - minimalną wartość, jaką widzieliśmy do tej pory, i maksymalny zysk, jaki moglibyśmy osiągnąć, mając tylko pierwsze k elementów. Początkowo minimalna wartość, jaką widzieliśmy do tej pory, to pierwszy element, a maksymalny zysk wynosi zero. Kiedy widzimy nowy element, najpierw aktualizujemy nasz optymalny zysk, obliczając, ile byśmy zarobili, kupując po najniższej dotychczasowej cenie i sprzedając po aktualnej cenie. Jeśli jest to lepsze niż optymalna wartość, którą do tej pory obliczyliśmy, aktualizujemy optymalne rozwiązanie, aby było to nowe zyski. Następnie aktualizujemy minimalny dotychczas widziany element, tak aby był minimalnym obecnym najmniejszym elementem i nowym elementem.
Ponieważ na każdym kroku wykonujemy tylko O (1) pracę i odwiedzamy każdy z n elementów dokładnie raz, wykonanie tego zajmuje O (n) czasu! Ponadto wykorzystuje tylko pamięć dyskową O (1). To jest tak dobre, jak dotychczas!
Jako przykład, na twoich danych wejściowych, oto jak ten algorytm może działać. Liczby pomiędzy każdą z wartości tablicy odpowiadają wartościom przechowywanym przez algorytm w tym momencie. W rzeczywistości nie przechowywałbyś ich wszystkich (zajęłoby to O (n) pamięci!), Ale pomocne jest obserwowanie ewolucji algorytmu:
Odpowiedź: (5, 10)
Odpowiedź: (4, 12)
Odpowiedź: (1, 5)
Czy możemy teraz zrobić lepiej? Niestety nie w asymptotycznym sensie. Jeśli użyjemy mniej niż O (n) czasu, nie możemy spojrzeć na wszystkie liczby na dużych wejściach, a tym samym nie możemy zagwarantować, że nie przegapimy optymalnej odpowiedzi (moglibyśmy po prostu „ukryć” ją w elementach, które nie patrzył). Ponadto nie możemy użyć mniej niż O (1) przestrzeni. Mogą istnieć pewne optymalizacje stałych czynników ukrytych w notacji duże-O, ale w przeciwnym razie nie możemy spodziewać się znalezienia radykalnie lepszych opcji.
Ogólnie oznacza to, że mamy następujące algorytmy:
Mam nadzieję że to pomoże!
EDYCJA : Jeśli jesteś zainteresowany, napisałem wersję Pythona tych czterech algorytmów , abyś mógł się nimi bawić i oceniać ich względne działanie. Oto kod:
źródło
To jest problem z maksymalną sumą podciągów z odrobiną niekierowania. Problem z maksymalną sumą podciągów ma listę liczb całkowitych, które mogą być dodatnie lub ujemne, znajdź największą sumę ciągłego podzbioru tej listy.
Możesz w prosty sposób przekształcić ten problem w ten problem, biorąc zysk lub stratę między kolejnymi dniami. Więc przekształciłbyś listę cen akcji, np. W
[5, 6, 7, 4, 2]
listę zysków / strat, np[1, 1, -3, -2]
. Problem z sumą podciągów jest wtedy całkiem łatwy do rozwiązania: znajdź podciąg o największej sumie elementów w tablicyźródło
Nie jestem pewien, dlaczego jest to uważane za pytanie dotyczące programowania dynamicznego. Widziałem to pytanie w podręcznikach i przewodnikach po algorytmach używających środowiska uruchomieniowego O (n log n) i O (log n) dla przestrzeni (np. Elementy wywiadów programistycznych). Wydaje się, że jest to znacznie prostszy problem, niż się ludziom wydaje.
Działa to poprzez śledzenie maksymalnego zysku, minimalnej ceny zakupu, a tym samym optymalnej ceny kupna / sprzedaży. Podczas przeglądania każdego elementu tablicy sprawdza, czy dany element jest mniejszy niż minimalna cena zakupu. Jeśli tak, minimalny indeks ceny zakupu (
min
) jest aktualizowany tak, aby był indeksem tego elementu. Dodatkowo dla każdego elementubecomeABillionaire
algorytm sprawdza, czyarr[i] - arr[min]
(różnica między obecnym elementem a minimalną ceną zakupu) jest większa od aktualnego zysku. Jeśli tak jest, zysk jest aktualizowany zgodnie z tą różnicą i kupuj,arr[min]
a sprzedawajarr[i]
.Działa w jednym przejeździe.
Współautor: https://stackoverflow.com/users/599402/ephraim
źródło
Problem jest identyczny z maksymalną sekwencją cząstkową,
którą rozwiązałem za pomocą programowania dynamicznego. Śledź bieżące i poprzednie (zysk, data zakupu i sprzedaży) Jeśli prąd jest wyższy niż poprzedni, zastąp poprzedni bieżącym.
źródło
oto rozwiązanie My Java:
źródło
Wymyśliłem proste rozwiązanie - kod jest bardziej zrozumiały. To jedno z pytań dotyczących programowania dynamicznego.
Kod nie zajmuje się sprawdzaniem błędów i skrajnymi przypadkami. To tylko próbka, która daje wyobrażenie o podstawowej logice rozwiązania problemu.
źródło
Oto moje rozwiązanie. modyfikuje algorytm maksymalnej sekwencji podrzędnej. Rozwiązuje problem w O (n). Myślę, że nie można tego zrobić szybciej.
źródło
Jest to interesujący problem, ponieważ wydaje się trudny, ale uważne przemyślenie daje eleganckie, oszczędne rozwiązanie.
Jak już wspomniano, można go rozwiązać brutalną siłą w czasie O (N ^ 2). Dla każdego wpisu w tablicy (lub liście) wykonaj iterację po wszystkich poprzednich wpisach, aby uzyskać wartość minimalną lub maksymalną, w zależności od tego, czy problemem jest znalezienie największego zysku czy straty.
Oto jak myśleć o rozwiązaniu w O (N): każdy wpis reprezentuje nowe możliwe maksimum (lub min). Następnie wszystko, co musimy zrobić, to zapisać poprzednie min (lub max) i porównać różnicę z bieżącym i poprzednim min (lub max). Bułka z masłem.
Oto kod w Javie jako test JUnit:
W przypadku wyliczenia największej straty śledzimy maksimum na liście (cenę kupna) aż do aktualnego wpisu. Następnie obliczamy różnicę między wartością maksymalną a bieżącą pozycją. Jeśli max - current> maxLoss, to zachowujemy tę różnicę jako nową maxLoss. Ponieważ indeks max jest na pewno mniejszy niż indeks bieżącego, gwarantujemy, że data „kupna” jest krótsza niż data „sprzedaży”.
W przypadku obliczenia największego zysku wszystko jest odwrotne. Śledzimy min na liście aż do aktualnego wpisu. Obliczamy różnicę między min a bieżącą pozycją (odwracając kolejność odejmowania). Jeśli current - min> maxGain, to zachowujemy tę różnicę jako nową wartość maxGain. Ponownie, indeks „kup” (min) znajduje się przed indeksem prądu („sprzedaj”).
Musimy tylko śledzić maxGain (lub maxLoss) i indeks min lub max, ale nie obu, i nie musimy porównywać indeksów, aby potwierdzić, że „kup” jest mniejsze niż „sprzedaj”, ponieważ uzyskać to naturalnie.
źródło
Maksymalny zysk ze sprzedaży jednostkowej, rozwiązanie O (n)
Oto projekt, który przeprowadza testy złożoności czasowej na podejściach o (N) vs o (n ^ 2) na losowym zestawie danych na 100 tys. Liczb całkowitych. O (n ^ 2) zajmuje 2 sekundy, a O (n) 0,01 s
https://github.com/gulakov/complexity.js
Jest to wolniejsze podejście, o (n ^ 2), które trwa przez resztę dni każdego dnia, podwójna pętla.
źródło
Najwyżej głosowana odpowiedź nie dopuszcza przypadków, w których maksymalny zysk jest ujemny i należy go zmodyfikować, aby uwzględnić takie przypadki. Można to zrobić, ograniczając zakres pętli do (len (a) - 1) i zmieniając sposób określania zysku poprzez przesunięcie indeksu o jeden.
Porównaj tę wersję funkcji z poprzednią dla tablicy:
źródło
źródło
Możliwością określenia maksymalnego zysku może być śledzenie elementów minimum po lewej stronie i maksimum po prawej stronie w tablicy przy każdym indeksie tablicy. Podczas iteracji kursów akcji w danym dniu poznasz najniższą cenę do tego dnia, a także maksymalną cenę po tym dniu (włącznie).
Na przykład zdefiniujmy a
min_arr
imax_arr
, przy czym podana tablica jestarr
. Indeksi
wmin_arr
byłby minimalnym elementem wearr
wszystkich indeksach<= i
(po lewej stronie i włącznie). Indeksi
wmax_arr
byłby maksymalnym elementem wearr
dla wszystkich indeksów>= i
(prawo do i włącznie). Wtedy możesz znaleźć maksymalną różnicę między odpowiednimi elementami wmax_arr
i `min_arr ':Powinno to działać w czasie O (n), ale uważam, że zajmuje dużo miejsca.
źródło
To maksymalna różnica między dwoma elementami w tablicy i to jest moje rozwiązanie:
O (N) złożoność czasowa O (1) złożoność przestrzenna
źródło
Po tym, jak nie zdałem egzaminu z kodowania na żywo na stanowisko inżyniera rozwiązań FB, musiałem rozwiązać go w spokojnej, chłodnej atmosferze, więc oto moje 2 centy:
źródło
Jedyną odpowiedzią naprawdę odpowiadającą na pytanie jest @akash_magoon (i to w tak prosty sposób!), Ale nie zwraca ona dokładnego obiektu określonego w pytaniu. Trochę refaktoryzowałem i moja odpowiedź w PHP zwracała tylko to, o co pytano:
źródło
Zgrabne rozwiązanie:
źródło
Ten program w pythonie3 może zwrócić cenę zakupu i cenę sprzedaży, która zmaksymalizuje zysk, obliczoną ze złożonością czasową O (n) i złożonością przestrzeni O (1) .
źródło
Oto moje rozwiązanie
źródło
Dla wszystkich odpowiedzi śledzących minimum i maksimum elementów, to rozwiązanie jest w rzeczywistości rozwiązaniem O (n ^ 2). Dzieje się tak, ponieważ na koniec należy sprawdzić, czy maksimum wystąpiło po minimum, czy nie. Jeśli tak się nie stało, wymagane są dalsze iteracje, aż warunek zostanie spełniony, a to pozostawia najgorszy przypadek O (n ^ 2). A jeśli chcesz pominąć dodatkowe iteracje, potrzeba dużo więcej miejsca. Tak czy inaczej, nie-nie w porównaniu z rozwiązaniem do programowania dynamicznego
źródło