Bei gegebenen n
Zahlen in einem Array (Sie können nicht davon ausgehen, dass es sich um Ganzzahlen handelt) möchte ich das Produkt aller Teilmengen der Größe berechnen n-1
.
Sie können dies tun, indem Sie alle Zahlen miteinander multiplizieren und dann nacheinander durch jede dividieren, solange keine der Zahlen Null ist. Wie schnell können Sie dies jedoch ohne Unterteilung tun?
Wenn Sie keine Division zulassen, wie viele arithmetische Operationen (z. B. Multiplikation und Addition) sind mindestens erforderlich, um das Produkt aller Teilmengen der Größe n-1 zu berechnen?
Natürlich können Sie es in (n-1)*n
Multiplikationen tun .
Zur Verdeutlichung handelt es sich bei der Ausgabe um n
verschiedene Produkte, und die einzigen Operationen, die außer Lesen und Schreiben in den Speicher zulässig sind, sind Multiplikation, Addition und Subtraktion.
Beispiel
Wenn die Eingabe drei Zahlen hat 2,3,5
, dann ist die Ausgabe drei Zahlen 15 = 3*5
, 10 = 2*5
und 6 = 2*3
.
Gewinnkriterium
Die Antworten sollten eine genaue Formel für die Anzahl der Rechenoperationen enthalten, für die der Code verwendet n
. Um das Leben einfacher zu machen, werde ich mich einfach n = 1000
an Ihre Formel anschließen, um deren Punktzahl zu beurteilen. Je niedriger desto besser.
Wenn es zu schwierig ist, eine genaue Formel für Ihren Code zu erstellen, können Sie sie einfach ausführen n = 1000
und die arithmetischen Operationen im Code zählen. Eine genaue Formel wäre jedoch am besten.
Sie sollten Ihre Punktzahl für n=1000
Ihre Antwort zum einfachen Vergleich hinzufügen .
quelle
+
auf Indizes ? Wenn dies der Fall ist, zählt dann auch die Array-Indizierung? (da es sich immerhin um syntaktischen Zucker zur Zugabe und Dereferenzierung handelt).(n-1)*n
Multiplikationen machen. Du meinst(n-2)*n
, richtig?Antworten:
Python, 3 (n-2) Operationen, Score = 2994
Die Arrays
left
undright
enthalten die kumulierten Produkte des Arrays von links bzw. von rechts.BEARBEITEN: Beweisen Sie, dass 3 (n-2) die optimale Anzahl von Operationen ist, die für n> = 2 benötigt werden, wenn wir nur die Multiplikation verwenden.
Wir werden das durch Induktion tun; Mit dem obigen Algorithmus müssen wir nur beweisen, dass für n> = 2, 3 (n-2) eine Untergrenze für die Anzahl der erforderlichen Multiplikationen ist.
Für n = 2 benötigen wir mindestens 0 = 3 (2-2) Multiplikationen, das Ergebnis ist also trivial.
Sei n> 2, und angenommen, für n - 1 Elemente brauchen wir mindestens 3 (n - 3) Multiplikationen. Betrachten Sie eine Lösung für n Elemente mit k Multiplikationen. Jetzt entfernen wir das letzte dieser Elemente wie 1 und vereinfachen alle Multiplikationen direkt damit. Wir entfernen auch die Multiplikation, die zum Produkt aller anderen Elemente führt, da dieses nicht benötigt wird, da es niemals als Zwischenwert verwendet werden kann, um das Produkt von n-2 der anderen Elemente zu erhalten, da eine Division nicht zulässig ist. Dies ergibt 1 Multiplikationen und eine Lösung für n - 1 Elemente.
Nach Induktionshypothese haben wir l> = 3 (n-3).
Schauen wir uns nun an, wie viele Multiplikationen entfernt wurden. Einer von ihnen war derjenige, der zum Produkt aller Elemente mit Ausnahme des letzten führte. Darüber hinaus wurde das letzte Element direkt in mindestens zwei Multiplikationen verwendet: Wenn es nur in einer verwendet wurde, wurde es beim Multiplizieren mit einem Zwischenergebnis verwendet, das aus einem Produkt der anderen Elemente bestand; Nehmen wir an, dieses Zwischenergebnis enthielt ohne Verlust der Allgemeinheit das erste Element im Produkt. Dann gibt es keine Möglichkeit, das Produkt aller Elemente außer dem ersten zu erhalten, da jedes Produkt, das das letzte Element enthält, entweder das letzte Element oder das erste Element enthält.
Wir haben also k> = 1 + 3> = 3 (n-2), was den behaupteten Satz beweist.
quelle
f l = zipWith (*) (scanl (*) 1 l) (scanr (*) 1 $ tail l)
.Haskell , Score 2994
Probieren Sie es online!
Sagen wir, wir bekommen die Liste
[a,b,c,d,e,f,g,h]
. Wir gruppieren es zuerst in Paare[[a,b],[c,d],[e,f],[g,h]]
. Dann greifen wir auf die halbe Listepairs
ihrer Produkte zurück, um sie zu bekommensubresults
Wenn wir das erste Element nehmen
(c*d)*(e*f)*(g*h)
und multiplizieren sie durchb
unda
jeweils erhalten wir das Produkt aller abera
und alle , aberb
. Tun wir dies für jedes Paar und rekursives Ergebnis, wobei dieses Paar fehlt, erhalten wir das endgültige Ergebnis. Der Fall mit ungerader Länge wird speziell behandelt, indem das ungerade Element ungepaart an den rekursiven Schritt übergeben wird und das Produkt der zurückgegebenen verbleibenden Elemente das Produkt ohne dieses Element ist.Die Anzahl der Multiplikationen
t(n)
giltn/2
für das Paarungsprodukt,t(n/2)
für den rekursiven Aufruf undn
für die Produkte mit einzelnen Elementen. Dies gibtt(n) = 1.5 * n + t(n/2)
für ungeraden
. Die Verwendung einer genaueren Anzahl für ungeraden
und das Ignorieren des Multiplizierens mit1
für den Basisfall ergibt eine Punktzahl2997
fürn=1000
.quelle
products_but_one'
dies verhindern, indem Sie etwas in der richtigen Länge zurücksenden.1
, die sich frei multiplizieren lässt. Ich denke, dass die Polsterung 1 die Dinge nicht beeinflusst hat, aber ich habe meinen Algorithmus aufgeräumt, um sie nicht zu verwenden.float
.Haskell , Punktzahl 9974
Probieren Sie es online!
Eine Divide-and-Conquer-Strategie, die sehr an Merge erinnert. Indiziert nicht.
Die Funktion
partition
teilt die Liste in möglichst gleiche Hälften, indem alternierende Elemente auf gegenüberliegenden Seiten der Partition platziert werden. Wir führen die Ergebnisse(p,r)
für jede der Hälften rekursiv zusammen , wobeir
die Liste der fehlenden Produkte undp
das Gesamtprodukt angezeigt werden.Für die Ausgabe der vollständigen Liste muss sich das fehlende Element in einer der Hälften befinden. Das Produkt, bei dem dieses Element fehlt, ist ein Produkt, bei dem die Hälfte fehlt, multipliziert mit dem vollständigen Produkt für die andere Hälfte. Wir multiplizieren also jedes fehlende Produkt mit dem vollständigen Produkt der anderen Hälfte und erstellen eine Liste der Ergebnisse
map(*p1)r2 ++ map(*p2)r1)
. Dies erfordertn
Multiplikationen, wobein
die Länge ist. Wir müssen auch ein neues Vollproduktp1*p2
für die zukünftige Verwendung erstellen, das 1 weitere Multiplikation kostet.Dies gibt die allgemeine Rekursion für die Anzahl der Operationen
t(n)
mitn
selbst:t(n) = n + 1 + 2 * t(n/2)
. Die ungerade ist ähnlich, aber eine der Unterlisten ist1
größer. Wenn wir die Rekursion herausrechnen, erhalten wirn*(log_2(n) + 1)
Multiplikationen, obwohl die gerade / ungerade Unterscheidung diesen exakten Wert beeinflusst. Die Werte von bis zut(3)
werden verbessert , indem nicht durch Multiplikation1
eine Variante durch die Definition(%)
von(*)
der , die Verknüpfungen_*1
oder1*_
Fällen.Dies ergibt
9975
Multiplikationen fürn=1000
. Ich glaube, Haskells Faulheit bedeutet, dass das nicht verwendete Gesamtprodukt in der äußeren Schicht nicht berechnet wird9974
. wenn ich mich irre, könnte ich es explizit weglassen.quelle
n = 1000
und die arithmetischen Operationen im Code zählen.9974
und nicht (im Fall der Berechnung des Gesamtprodukts in der äußeren Schicht). Haben Sie ein in die Eingabe aufgenommen, mit der Sie es getestet haben?9975
n = 1000
1
trace
ausDebug.Trace
mit einer catch-all| trace "call!" False = undefined
Wache, glaube ich. Aber das wirdunsafePerformIO
unter der Haube verwendet, also ist es keine wirkliche Verbesserung.Haskell , Score 2994
Probieren Sie es online!
Wie es funktioniert
Dies ist eine bereinigte Version von xnors Algorithmus , die den ungeraden Fall auf einfachere Weise behandelt (Bearbeiten: Es sieht so aus, als hätte xnor ihn auf die gleiche Weise bereinigt):
[a, b, c, d, e, f, g] ↦
[a, bc, de, fg] ↦
[(bc) (de) (fg), a (de) (fg), a (bc) ( fg), a (bc) (de)] durch Rekursion ↦
[(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)] durch Rekursion ↦
[(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].
quelle
O (n log n) Operationen, Punktzahl = 9974
Arbeitet mit einem binären Baum.
Python
Dies erfordert auch Operationen zum Hinzufügen von Listen und einige Berechnungen für Zahlen, die nicht die Eingabewerte sind. nicht sicher, ob das zählt. Die
mul
Funktion dient zum Speichern von n Operationen für den Basisfall, um deren Verschwendung durch Multiplikation mit 1 zu vermeiden. In jedem Fall handelt es sich um O (n log n) -Operationen. Die genaue Formel ist, wenn nur arithmetische Operationen an Eingangszahlen zu zählen, mitj = floor(log_2(n))
:j * (2^(j + 1) - n) + (j + 1) * (2 * n - 2^(j + 1)) - 2
.Vielen Dank an @xnor für das Speichern eines Vorgangs mit der Idee, das äußere Produkt nicht zu berechnen!
Der letzte Teil ist die Ausgabe der Produkte in der Reihenfolge des fehlenden Begriffs.
quelle
n = 1000
und die arithmetischen Operationen im Code zählen.p[i] = p[i + i] * p[i + i+1]
wird nicht gezähltn log2 n + n
Operationen (das ist O (nlogn) btwp[i] = p[i + i] * p[i + i + 1]
sollten durch die Multiplikationsoptimierung gespeichert werden. Ich hätte aber vielleicht einen zu viel gezählt.O ((n-2) * n) = O (n 2 ): Triviale Lösung
Dies ist nur die einfache Lösung, mit der jede Teilmenge multipliziert wird:
Python
Beachten Sie, dass dazu auch
n
Operationen zum Hinzufügen von Listen erforderlich sind . nicht sicher, ob das zählt. Wenn das nicht erlaubt ist, dannproduct(array[:index] + array[index + 1:])
kann zu ersetzt werdenproduct(array[:index]) * product(array[index + 1:])
, wodurch sich die Formel zu ändertO((n-1)*n)
.quelle
product
Funktionsoperationen nichtO(n)
? eines für jedes Element im Array (obwohl dies leicht geändert werden kannO(n-1)
)Python, 7540
Eine dreigliedrige Fusionsstrategie. Ich denke, ich kann es noch besser machen, mit einer noch größeren Verschmelzung. Es ist O (n log n).
BEARBEITEN: Ein Fehler wurde behoben.
Die relevante Funktion ist
missing_products
, die das Gesamtprodukt und alle mit einem fehlenden Element gibt.quelle
tri_merge
? Auch Sie können das2 * split_size + ...
intri_partition
mit ersetzensplit_size + split_size + ...
.dc, score 2994
Ich gehe davon aus, dass der ganzzahlige Vergleich
z1=
(der die Rekursion beendet, wenn wir den letzten Wert erreichen) frei ist. Dies ist äquivalent zu Gleichenforeach
in anderen Sprachen.Vorführungen
Eine Demo mit großen und kleinen Eingaben:
quelle
C ++, Score: 5990, O ([2NlogN] / 3)
Diese Implementierung verwendet eine Binärbaum-Nachschlagetabelle. Meine erste Implementierung war O (NlogN), aber eine Last-Minute-Optimierung, die das Produkt aller Array-Elemente abzüglich eines Paares nachschlägt, + 2 Multiplikationen sparten den Tag. Ich denke, das könnte noch ein bisschen weiter optimiert werden, vielleicht noch 16% ...
Ich habe einige Fehlerbehebungsspuren hinterlassen, nur weil es einfacher ist, sie zu löschen, als sie neu zu schreiben :)
[Bearbeiten] Die tatsächliche Komplexität wird bei O ([2NlogN] / 3) für 100 gemessen. Sie ist bei kleinen Mengen tatsächlich etwas schlechter als O (NlogN), tendiert jedoch zu O ([NlogN] / 2), wenn das Array wächst sehr großes O (0,57.NlogN) für eine Menge von 1 Million Elementen.
Der Vollständigkeit halber füge ich den Algorithmus von @ nore hinzu. Es ist wirklich schön und am schnellsten.
quelle