Biorąc pod uwagę n
liczby w tablicy (nie można zakładać, że są to liczby całkowite), chciałbym obliczyć iloczyn wszystkich podzbiorów wielkości n-1
.
Możesz to zrobić, mnożąc wszystkie liczby, a następnie dzieląc je kolejno, o ile żadna z liczb nie jest równa zero. Jak szybko możesz to zrobić bez podziału?
Jeśli nie pozwalasz na dzielenie, jaka jest minimalna liczba operacji arytmetycznych (np. Mnożenie i dodawanie) potrzebnych do obliczenia iloczynu wszystkich podzbiorów wielkości n-1?
Oczywiście możesz to zrobić w (n-1)*n
multiplikacjach.
Aby wyjaśnić, dane wyjściowe są n
różnymi produktami, a jedynymi dozwolonymi operacjami oprócz odczytu i zapisu do pamięci są mnożenie, dodawanie i odejmowanie.
Przykład
Jeżeli wejście ma trzy numery 2,3,5
, to wyjście jest trzy numery 15 = 3*5
, 10 = 2*5
i 6 = 2*3
.
Kryterium wygranej
Odpowiedzi powinny zawierać dokładną formułę liczby operacji arytmetycznych, których użyje ich kod n
. Aby ułatwić życie, po prostu podłączę się n = 1000
do twojej formuły, aby ocenić jej wynik. Im niższy, tym lepiej.
Jeśli zbyt trudno jest stworzyć dokładną formułę dla swojego kodu, możesz po prostu uruchomić go n = 1000
i policzyć operacje arytmetyczne w kodzie. Dokładna formuła byłaby jednak najlepsza.
Powinieneś dodać swój wynik n=1000
do swojej odpowiedzi w celu łatwego porównania.
+
na indeksach liczyć? Jeśli tak jest, czy liczy się również indeksowanie tablic? (ponieważ jest to przecież cukier syntaktyczny do dodawania i dereferencji).(n-1)*n
multiplikacjach. Masz na myśli(n-2)*n
, prawda?Odpowiedzi:
Python, 3 (n-2) operacje, wynik = 2994
Tablice
left
iright
zawierają skumulowane produkty tablicy odpowiednio z lewej i prawej strony.EDYCJA: Dowód, że 3 (n-2) jest optymalną liczbą operacji potrzebnych dla n> = 2, jeśli używamy tylko mnożenia.
Zrobimy to przez indukcję; powyższym algorytmem musimy tylko udowodnić, że dla n> = 2, 3 (n-2) stanowi dolną granicę liczby potrzebnych mnożenia.
Dla n = 2 potrzebujemy co najmniej 0 = 3 (2-2) krotności, więc wynik jest trywialny.
Niech n> 2 i załóżmy, że dla elementów n - 1 potrzebujemy co najmniej 3 (n-3) mnożenia. Rozważ rozwiązanie n elementów z k mnożeniem. Teraz usuwamy ostatni z tych elementów, tak jakby to był 1, i bezpośrednio upraszczamy wszystkie multiplikacje. Usuwamy również mnożenie prowadzące do iloczynu wszystkich pozostałych elementów, ponieważ ten nie jest potrzebny, ponieważ nigdy nie można go użyć jako wartości pośredniej do uzyskania iloczynu n-2 pozostałych elementów, ponieważ podział nie jest dozwolony. To daje nam mnożenie 1 i rozwiązanie dla n - 1 elementów.
Zgodnie z hipotezą indukcyjną mamy l> = 3 (n-3).
Teraz rzućmy okiem na to, ile multiplikacji zostało usuniętych. Jednym z nich był ten, który doprowadził do iloczynu wszystkich elementów oprócz ostatniego. Co więcej, ostatni element został użyty bezpośrednio w co najmniej dwóch multiplikacjach: jeśli został użyty tylko w jednym, to został użyty przy pomnożeniu przez wynik pośredni składający się z iloczynu innych elementów; powiedzmy, bez utraty ogólności, ten pośredni wynik obejmował pierwszy element produktu. Zatem nie ma sposobu na uzyskanie iloczynu wszystkich elementów oprócz pierwszego, ponieważ każdy produkt, który zawiera ostatni element, jest albo ostatnim elementem, albo zawiera pierwszy element.
Mamy zatem k> = l + 3> = 3 (n-2), co dowodzi twierdzonego twierdzenia.
źródło
f l = zipWith (*) (scanl (*) 1 l) (scanr (*) 1 $ tail l)
.Haskell , wynik 2994
Wypróbuj online!
Powiedzmy, że dostaliśmy listę
[a,b,c,d,e,f,g,h]
. Najpierw grupujemy je w pary[[a,b],[c,d],[e,f],[g,h]]
. Następnie powracamy do listypairs
produktów o pół wielkości , aby uzyskaćsubresults
Jeżeli weźmiemy pod uwagę pierwszy element
(c*d)*(e*f)*(g*h)
, i pomnożyć przezb
ia
odpowiednio, otrzymujemy produkt o wszystkich, alea
i wszystkich, aleb
. Robiąc to dla każdej pary i wyniku rekurencyjnego z brakującą parą, otrzymujemy wynik końcowy. Przypadek nieparzystej długości jest obsługiwany w specjalny sposób, gdy nieparzysty element jest przekazywany niesparowany do kroku rekurencyjnego, a iloczyn pozostałych elementów jest produktem bez niego.Liczba mnożeń
t(n)
dotyczyn/2
produktu parowania,t(n/2)
połączenia rekurencyjnego, a drugiegon
dla produktów z pojedynczymi elementami. To dajet(n) = 1.5 * n + t(n/2)
dziwnen
. Użycie bardziej precyzyjnego liczenia dla nieparzystychn
i ignorowanie mnożenia1
dla przypadku podstawowego daje wynik2997
dlan=1000
.źródło
products_but_one'
może tego uniknąć, zwracając coś o odpowiedniej długości.1
swobodę mnożenia. Myślę, że wypełnienie 1 nie wpłynęło na rzeczy, ale wyczyściłem algorytm, aby ich nie używać.float
.Haskell , wynik 9974
Wypróbuj online!
Strategia „dziel i rządź”, bardzo przypominająca rodzaj scalania. Nie wykonuje żadnego indeksowania.
Ta funkcja
partition
dzieli listę na możliwie równe połowy, umieszczając naprzemienne elementy po przeciwnych stronach partycji. Rekurencyjnie łączymy wyniki(p,r)
dla każdej z połówek, zr
listą produktów z jednym brakującym ip
ogólnym produktem.W przypadku danych wyjściowych pełnej listy brakujący element musi znajdować się w jednej z połówek. Produkt, w którym brakuje tego elementu, to produkt, w którym brakuje połowy jego połowy, pomnożony przez pełny produkt dla drugiej połowy. Tak więc mnożymy każdy brakujący produkt przez pełny produkt drugiej połowy i tworzymy listę wyników, as
map(*p1)r2 ++ map(*p2)r1)
. Wymaga ton
mnożenia, gdzien
jest długość. Musimy także stworzyć nowy pełny produktp1*p2
do przyszłego użytku, kosztując 1 dodatkowy mnożenie.Daje to ogólny dla rekursji dla liczby operacji
t(n)
zn
jeszcze:t(n) = n + 1 + 2 * t(n/2)
. Dziwny jest podobny, ale jedna z podlist jest1
większa. Wykonując rekurencję, otrzymujemyn*(log_2(n) + 1)
mnożenia, chociaż rozróżnienie nieparzyste / parzyste wpływa na tę dokładną wartość. Wartości aż dot(3)
są poprawione przez pomnożenie przez nie1
definiując wariant(%)
z(*)
tym, że Skróty_*1
lub1*_
przypadkach.Daje to
9975
mnożenia dlan=1000
. Uważam, że lenistwo Haskella oznacza, że nieużywany ogólny produkt w zewnętrznej warstwie nie jest obliczany9974
; jeśli się mylę, mogę to pominąć.źródło
n = 1000
i policz operacje arytmetyczne w kodzie.9974
a nie do9975
mnożenian = 1000
(w przypadku obliczania całego produktu w warstwie zewnętrznej). Czy umieściłeś1
w danych wejściowych użytych do przetestowania?trace
z ochronąDebug.Trace
typu catch-all| trace "call!" False = undefined
. Ale używa się tegounsafePerformIO
pod maską, więc tak naprawdę nie jest to tak duża poprawa.Haskell , wynik 2994
Wypróbuj online!
Jak to działa
To jest oczyszczona wersja algorytmu xnor, która zajmuje się nieparzystym przypadkiem w bardziej prosty sposób (edycja: wygląda na to, że xnor wyczyścił go w ten sam sposób):
[a, b, c, d, e, f, g] ↦
[a, bc, de, fg] ↦
[(bc) (de) (fg), a (de) (fg), a (bc) ( fg), a (bc) (de)] przez rekurencję ↦
[(bc) (de) (fg), a (de) (fg) c, a (de) (fg) b, a (bc) (fg) e, a (bc) (fg) d, a (bc) (de) g, a (bc) (de) f]
[a, b, c, d, e, f, g, h] ↦
[ab, cd, ef, gh] ↦
[(cd) (ef) (gh), (ab) (ef) (gh), ( ab) (cd) (gh), (ab) (cd) (ef)] przez rekurencję ↦
[(cd) (ef) (gh) b, (cd) (ef) (gh) a, (ab) (ef ) (gh) d, (ab) (ef) (gh) c, (ab) (cd) (gh) f, (ab) (cd) (gh) e, (ab) (cd) (ef) h, (ab) (cd) (ef) g].
źródło
Operacje O (n log n), wynik = 9974
Działa z drzewem binarnym.
Pyton
Wymaga to również operacji dodawania listy i pewnej arytmetyki liczb, które nie są wartościami wejściowymi; nie jestem pewien, czy to się liczy.
mul
Funkcja jest tam, aby zaoszczędzić n operacje dla przypadku bazowego, aby uniknąć marnowania ich przez pomnożenie przez 1. W każdym przypadku jest to O (n log n) operacji. Dokładna formuła jest, jeśli tylko licząc operacji arytmetycznych na liczbach wejściowego,j = floor(log_2(n))
:j * (2^(j + 1) - n) + (j + 1) * (2 * n - 2^(j + 1)) - 2
.Dzięki @xnor za uratowanie jednej operacji z pomysłem nie obliczania zewnętrznego produktu!
Ostatnią częścią jest wyprowadzenie produktów w kolejności brakującego terminu.
źródło
n = 1000
i policz operacje arytmetyczne w kodzie.p[i] = p[i + i] * p[i + i+1]
nie jest liczonyn log2 n + n
operacje (czyli O (nlogn) btwp[i] = p[i + i] * p[i + i + 1]
powinny być zapisane przez optymalizację mnożenia. Mógłbym jednak policzyć o jedną za dużo.O ((n-2) * n) = O (n 2 ): Trywialne rozwiązanie
To tylko trywialne rozwiązanie, które zwielokrotnia każdy z podzbiorów:
Pyton
Zauważ, że wymaga to również
n
operacji dodawania listy; nie jestem pewien, czy to się liczy. Jeśli nie jest to dozwolone,product(array[:index] + array[index + 1:])
można je zastąpićproduct(array[:index]) * product(array[index + 1:])
, co zmienia formułę naO((n-1)*n)
.źródło
product
funkcjiO(n)
? jeden dla każdego elementu w tablicy (chociaż można to łatwo zmienić naO(n-1)
)Python, 7540
Trójstronna strategia scalania. Myślę, że mogę zrobić nawet lepiej niż to, z jeszcze dużym połączeniem. To O (n log n).
EDYCJA: Naprawiono błędne konto.
Odpowiednią funkcją jest
missing_products
, która daje ogólny produkt i wszystkie z brakującym elementem.źródło
tri_merge
? Ponadto można wymienić2 * split_size + ...
wtri_partition
zsplit_size + split_size + ...
.dc, wynik 2994
Zakładam, że porównanie liczb całkowitych
z1=
(które kończy rekurencję po osiągnięciu ostatniej wartości) jest bezpłatne. Jest to równoważne z podobnymiforeach
w innych językach.Pokazy
Demo z dużymi i małymi wejściami:
źródło
C ++, wynik: 5990, O ([2NlogN] / 3)
Ta implementacja korzysta z tabeli wyszukiwania drzewa binarnego. Moją pierwszą implementacją było O (NlogN), ale optymalizacja w ostatniej chwili, która sprawdza iloczyn wszystkich elementów tablicy minus para, + 2 mnożenia uratowało dzień. Myślę, że można to jeszcze trochę zoptymalizować, może kolejne 16% ...
Zostawiłem trochę śladów debugowania, tylko dlatego, że łatwiej je usunąć niż przepisać :)
[Edytuj] rzeczywista złożoność jest mierzona przy O ([2NlogN] / 3) dla 100. W rzeczywistości jest nieco gorsza niż O (NlogN) dla małych zestawów, ale zmierza w kierunku O ([NlogN] / 2) wraz ze wzrostem tablicy bardzo duże O (0,57.NlogN) dla zestawu 1 miliona elementów.
Dla kompletności dodam algorytm @ nore. To naprawdę miłe i najszybsze.
źródło