Étant donné les n
nombres dans un tableau (vous ne pouvez pas supposer que ce sont des entiers), je voudrais calculer le produit de tous les sous-ensembles de taille n-1
.
Vous pouvez le faire en multipliant tous les nombres ensemble, puis en les divisant à tour de rôle, tant qu'aucun des nombres n'est nul. Cependant, à quelle vitesse pouvez-vous le faire sans division?
Si vous n'autorisez pas la division, quel est le nombre minimum d'opérations arithmétiques (par exemple multiplication et addition) nécessaires pour calculer le produit de tous les sous-ensembles de taille n-1?
De toute évidence, vous pouvez le faire en (n-1)*n
multiplications.
Pour clarifier, la sortie est n
différents produits et les seules opérations en dehors de la lecture et de l' écriture à la mémoire sont permis la multiplication, l' addition et la soustraction.
Exemple
Si l'entrée a trois nombres 2,3,5
, alors la sortie est trois nombres 15 = 3*5
, 10 = 2*5
et 6 = 2*3
.
Critère gagnant
Les réponses devraient donner une formule exacte pour le nombre d'opérations arithmétiques que leur code utilisera en termes de n
. Pour vous simplifier la vie, je vais simplement brancher n = 1000
votre formule pour juger de son score. Le plus bas sera le mieux.
S'il est trop difficile de produire une formule exacte pour votre code, vous pouvez simplement l'exécuter n = 1000
et compter les opérations arithmétiques dans le code. Une formule exacte serait cependant préférable.
Vous devez ajouter votre score pour n=1000
à votre réponse pour une comparaison facile.
la source
+
sur les indices comptent-elles? Si tel est le cas, l'indexation des tableaux compte-t-elle également? (car c'est après tout du sucre syntaxique pour l'addition et le déréférencement).(n-1)*n
multiplications Vous voulez dire(n-2)*n
, non?Réponses:
Python, 3 (n-2) opérations, score = 2994
Les tableaux
left
etright
contiennent les produits cumulés du tableau de gauche et de droite, respectivement.EDIT: Preuve que 3 (n-2) est le nombre optimal d'opérations nécessaires pour n> = 2, si nous utilisons uniquement la multiplication.
Nous le ferons par induction; par l'algorithme ci-dessus, il suffit de prouver que pour n> = 2, 3 (n-2) est une borne inférieure du nombre de multiplications nécessaires.
Pour n = 2, nous avons besoin d'au moins 0 = 3 (2-2) multiplications, donc le résultat est trivial.
Soit n> 2, et supposons que pour n - 1 éléments, nous ayons besoin d'au moins 3 (n-3) multiplications. Considérons une solution pour n éléments avec k multiplications. Maintenant, nous supprimons le dernier de ces éléments comme s'il s'agissait de 1 et simplifions directement toutes les multiplications par lui. Nous supprimons également la multiplication conduisant au produit de tous les autres éléments, car celui-ci n'est pas nécessaire car il ne peut jamais être utilisé comme valeur intermédiaire pour obtenir le produit de n-2 des autres éléments, car la division n'est pas autorisée. Cela nous laisse avec l multiplications, et une solution pour n - 1 éléments.
Par hypothèse d'induction, nous avons l> = 3 (n-3).
Voyons maintenant combien de multiplications ont été supprimées. L'un d'eux était celui qui conduisait au produit de tous les éléments sauf le dernier. De plus, le dernier élément a été utilisé directement dans au moins deux multiplications: s'il a été utilisé dans un seul, alors il a été utilisé lors de la multiplication par un résultat intermédiaire consistant en un produit des autres éléments; disons, sans perte de généralité, ce résultat intermédiaire incluait le premier élément du produit. Ensuite, il n'y a aucun moyen d'obtenir le produit de tous les éléments sauf le premier, car chaque produit qui contient le dernier élément est soit le dernier élément, soit contient le premier élément.
Nous avons donc k> = l + 3> = 3 (n-2), prouvant le théorème revendiqué.
la source
f l = zipWith (*) (scanl (*) 1 l) (scanr (*) 1 $ tail l)
.Haskell , score 2994
Essayez-le en ligne!
Disons qu'on nous donne la liste
[a,b,c,d,e,f,g,h]
. Nous le groupons d'abord par paires[[a,b],[c,d],[e,f],[g,h]]
. Ensuite, nous recurse sur la liste demi-taillepairs
de leurs produits pour obtenirsubresults
Si nous prenons le premier élément
(c*d)*(e*f)*(g*h)
et le multiplions parb
eta
respectivement, nous obtenons le produit de tout saufa
et de tout saufb
. En faisant cela pour chaque paire et résultat récursif avec cette paire manquante, nous obtenons le résultat final. Le cas de longueur impaire est spécialement traité en faisant passer l'élément impair non apparié à l'étape récursive, et le produit des éléments restants retournés est le produit sans lui.Le nombre de multiplications
t(n)
estn/2
pour le produit d'appariement,t(n/2)
pour l'appel récursif et un autren
pour les produits avec des éléments individuels. Cela donnet(n) = 1.5 * n + t(n/2)
pour étrangen
. Utiliser un décompte plus précis pour les impairsn
et ignorer la multiplication avec1
pour le cas de base donne un score2997
pourn=1000
.la source
products_but_one'
pourrait éviter en retour quelque chose de la bonne longueur.1
qui est libre de se multiplier. Je pense que le padding 1 n'a pas affecté les choses, mais j'ai nettoyé mon algorithme pour ne pas les utiliser.float
.Haskell , score 9974
Essayez-le en ligne!
Une stratégie diviser pour mieux régner, qui rappelle beaucoup le tri par fusion. Ne fait aucune indexation.
La fonction
partition
divise la liste en deux moitiés aussi égales que possible en plaçant des éléments alternés sur les côtés opposés de la partition. Nous fusionnons récursivement les résultats(p,r)
pour chacune des moitiés, avecr
la liste des produits avec un manquant etp
le produit global.Pour la sortie de la liste complète, l'élément manquant doit se trouver dans l'une des moitiés. Le produit avec cet élément manquant est un produit manquant pour la moitié dans laquelle il se trouve, multiplié par le produit complet pour l'autre moitié. Ainsi, nous multiplions chaque produit avec un manquant par le produit complet de l'autre moitié et dressons une liste des résultats, au fur et à mesure
map(*p1)r2 ++ map(*p2)r1)
. Cela prend desn
multiplications, oùn
est la longueur. Nous devons également créer un nouveau produit completp1*p2
pour une utilisation future, ce qui coûtera 1 multiplication supplémentaire.Cela donne la récursion générale pour le nombre d'opérations
t(n)
avecn
même:t(n) = n + 1 + 2 * t(n/2)
. L'impaire est similaire, mais l'une des sous-listes est1
plus grande. En effectuant la récursivité, nous obtenons desn*(log_2(n) + 1)
multiplications, bien que la distinction impaire / paire affecte cette valeur exacte. Les valeurs jusqu'àt(3)
sont améliorées en ne se multipliant pas1
en définissant une variante(%)
de ce(*)
qui raccourcit le_*1
ou les1*_
cas.Cela donne des
9975
multiplications pourn=1000
. Je crois que la paresse de Haskell signifie que le produit global inutilisé dans la couche externe n'est pas calculé9974
; si je me trompe, je pourrais l'omettre explicitement.la source
n = 1000
et à compter les opérations arithmétiques dans le code.9974
et non les9975
multiplicationsn = 1000
(dans le cas du calcul du produit global dans la couche externe). Avez-vous inclus un1
dans l'entrée que vous avez utilisée pour le tester?trace
partirDebug.Trace
d'un fourre-tout| trace "call!" False = undefined
garde, je pense. Mais cela utiliseunsafePerformIO
sous le capot, donc ce n'est pas vraiment une grande amélioration.Haskell , score 2994
Essayez-le en ligne!
Comment ça marche
Il s'agit d'une version nettoyée de l'algorithme de xnor qui traite le cas étrange de manière plus simple (modification: il semble que xnor l'ait nettoyé de la même manière):
[a, b, c, d, e, f, g] ↦
[a, bc, de, fg] ↦
[(bc) (de) (fg), a (de) (fg), a (bc) ( fg), a (bc) (de)] par récursivité ↦
[(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)] par récursivité ↦
[(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].
la source
O (n log n) opérations, score = 9974
Fonctionne avec un arbre binaire.
Python
Cela nécessite également des opérations d'addition de liste et une certaine arithmétique sur les nombres qui ne sont pas les valeurs d'entrée; Je ne sais pas si cela compte. La
mul
fonction est là pour enregistrer n opérations pour le cas de base, pour éviter de les gaspiller en les multipliant par 1. Dans tous les cas, il s'agit d'O (n log n) opérations. Si seulement le comptage des opérations arithmétiques sur des nombres d'entrée, est, la formule exacte avecj = floor(log_2(n))
:j * (2^(j + 1) - n) + (j + 1) * (2 * n - 2^(j + 1)) - 2
.Merci à @xnor d'avoir sauvé une opération avec l'idée de ne pas calculer le produit extérieur!
La dernière partie consiste à sortir les produits dans l'ordre du terme manquant.
la source
n = 1000
et à compter les opérations arithmétiques dans le code.p[i] = p[i + i] * p[i + i+1]
n'est pas comptén log2 n + n
opérations (qui est O (nlogn) btwp[i] = p[i + i] * p[i + i + 1]
doivent être enregistrées par l'optimisation de la multiplication. J'aurais peut-être pu en compter un de trop, cependant.O ((n-2) * n) = O (n 2 ): Solution triviale
Ceci est juste la solution triviale qui multiplie ensemble chacun des sous-ensembles:
Python
Notez que cela nécessite également
n
des opérations d'ajout de liste; Je ne sais pas si cela compte. Si cela n'est pas autorisé, vousproduct(array[:index] + array[index + 1:])
pouvez le remplacer parproduct(array[:index]) * product(array[index + 1:])
, ce qui change la formule enO((n-1)*n)
.la source
product
fonctionO(n)
? un pour chaque élément du tableau (bien que cela puisse facilement être changé enO(n-1)
)Python, 7540
Une stratégie de fusion tripartite. Je pense que je peux faire encore mieux que cela, avec une fusion encore grande. C'est O (n log n).
EDIT: correction d'une erreur de calcul.
La fonction pertinente est
missing_products
, qui donne le produit global et tous ceux avec un élément manquant.la source
tri_merge
? Aussi , vous pouvez remplacer le2 * split_size + ...
danstri_partition
avecsplit_size + split_size + ...
.dc, score 2994
Je suppose que la comparaison entière
z1=
(qui met fin à la récursivité lorsque nous atteignons la dernière valeur) est libre. C'est équivalent aux goûts deforeach
dans d'autres langues.Démonstrations
Une démo avec de grandes et petites entrées:
la source
C ++, score: 5990, O ([2NlogN] / 3)
Cette implémentation utilise une table de recherche d'arbre binaire. Ma première implémentation a été O (NlogN), mais une optimisation de dernière minute, qui recherche le produit de tous les éléments du tableau moins une paire, + 2 multiplications ont sauvé la journée. Je pense que cela pourrait encore être optimisé un peu plus, peut-être encore 16% ...
J'ai laissé quelques traces de débogage, uniquement parce qu'il est plus facile de les supprimer que de les réécrire :)
[Modifier] la complexité réelle est mesurée à O ([2NlogN] / 3) pour 100. Elle est en fait un peu pire que O (NlogN) pour les petits ensembles, mais tend vers O ([NlogN] / 2) à mesure que le tableau grandit très grand O (0,57.NlogN) pour un ensemble de 1 million d'éléments.
J'ajoute l'algorithme de @ nore, pour être complet. C'est vraiment sympa et c'est le plus rapide.
la source