Étant donné un tableau de nombres, retourne un tableau de produits de tous les autres nombres (sans division)


186

On m'a posé cette question lors d'un entretien d'embauche et j'aimerais savoir comment les autres pourraient la résoudre. Je suis plus à l'aise avec Java, mais les solutions dans d'autres langues sont les bienvenues.

Étant donné un tableau de nombres,, numsrenvoie un tableau de nombres products, où products[i]est le produit de tous nums[j], j != i.

Input : [1, 2, 3, 4, 5]
Output: [(2*3*4*5), (1*3*4*5), (1*2*4*5), (1*2*3*5), (1*2*3*4)]
      = [120, 60, 40, 30, 24]

Vous devez le faire O(N)sans utiliser de division.


49
Cette question a été soulevée à quelques reprises au cours de la dernière semaine environ; interviewez-vous tous avec la même entreprise? :)
Michael Mrozek

Je suis actuellement en train de parcourir la [interview-questions]balise à sa recherche. Avez-vous un lien si vous l'avez trouvé?
polygenelubricants

2
@Michael: Cette question permet la division. Le mien l'interdit explicitement. Je dirais que ce sont deux questions différentes.
polygenelubricants

8
Remplacez la division par log (a / b) = log (a) -log (b) et voila!
ldog le

1
imaginez s'il y a 1 ou plus de 1 zéros dans le tableau, comment allez-vous gérer le cas ??
gst

Réponses:


257

Une explication de la méthode des lubrifiants polygéniques est: L'astuce consiste à construire les tableaux (dans le cas de 4 éléments)

{              1,         a[0],    a[0]*a[1],    a[0]*a[1]*a[2],  }
{ a[1]*a[2]*a[3],    a[2]*a[3],         a[3],                 1,  }

Les deux peuvent être effectués en O (n) en commençant respectivement par les bords gauche et droit.

Ensuite, multiplier les deux éléments du tableau par élément donne le résultat requis

Mon code ressemblerait à ceci:

int a[N] // This is the input
int products_below[N];
p=1;
for(int i=0;i<N;++i) {
  products_below[i]=p;
  p*=a[i];
}

int products_above[N];
p=1;
for(int i=N-1;i>=0;--i) {
  products_above[i]=p;
  p*=a[i];
}

int products[N]; // This is the result
for(int i=0;i<N;++i) {
  products[i]=products_below[i]*products_above[i];
}

Si vous devez également être O (1) dans l'espace, vous pouvez le faire (ce qui est moins clair à mon humble avis)

int a[N] // This is the input
int products[N];

// Get the products below the current index
p=1;
for(int i=0;i<N;++i) {
  products[i]=p;
  p*=a[i];
}

// Get the products above the curent index
p=1;
for(int i=N-1;i>=0;--i) {
  products[i]*=p;
  p*=a[i];
}

4
C'est le runtime O (n) mais c'est aussi O (n) en complexité spatiale. Vous pouvez le faire dans l'espace O (1). Je veux dire, autre que la taille des conteneurs d'entrée et de sortie bien sûr.
wilhelmtell

8
Très intelligent! Y a-t-il un nom pour cet algorithme?
fastcodejava

2
@MichaelAnderson Excellent homme de travail, mais s'il vous plaît dites-moi la logique principale derrière cela et comment avez-vous commencé cela une fois que vous avez obtenu l'exigence.
ACBalaji

3
L'algorithme échouera si l'un des éléments est égal à 0. N'oubliez donc pas de cocher le 0 pour sauter.
Mani

2
@Mani L'algorithme est parfait s'il y a des éléments mis à 0. Cependant, il peut être possible de balayer l'entrée pour de tels éléments et d'être plus efficace s'ils sont trouvés. S'il y a deux éléments nuls, le résultat entier est zéro, et s'il n'y en a qu'un, disons v_i=0que la seule entrée non nulle dans le résultat est le ième élément. Cependant, je soupçonne que l'ajout d'une passe pour détecter et compter les éléments nuls nuirait à la clarté de la solution, et ne ferait probablement pas de réel gain de performance dans la majorité des cas.
Michael Anderson

52

Voici une petite fonction récursive (en C ++) pour faire la modofication en place. Il nécessite cependant O (n) espace supplémentaire (sur la pile). En supposant que le tableau est dans a et que N contient la longueur du tableau, nous avons

int multiply(int *a, int fwdProduct, int indx) {
    int revProduct = 1;
    if (indx < N) {
       revProduct = multiply(a, fwdProduct*a[indx], indx+1);
       int cur = a[indx];
       a[indx] = fwdProduct * revProduct;
       revProduct *= cur;
    }
    return revProduct;
}

Quelqu'un pourrait-il expliquer cette récursivité?
nikhil

1
@nikhil Il fait d'abord la récursion, se souvenant des produits intermédiaires, formant finalement le produit numérique pour num[N-1]; puis sur le chemin du retour il calcule la seconde partie de la multiplication qui sert ensuite à modifier le tableau numérique en place.
Ja͢ck

imaginez s'il y a 1 ou plus de 1 zéros dans le tableau, comment allez-vous gérer le cas ??
gst

18

Voici ma tentative de le résoudre en Java. Toutes mes excuses pour le formatage non standard, mais le code a beaucoup de duplication, et c'est le mieux que je puisse faire pour le rendre lisible.

import java.util.Arrays;

public class Products {
    static int[] products(int... nums) {
        final int N = nums.length;
        int[] prods = new int[N];
        Arrays.fill(prods, 1);
        for (int
           i = 0, pi = 1    ,  j = N-1, pj = 1  ;
           (i < N)         && (j >= 0)          ;
           pi *= nums[i++]  ,  pj *= nums[j--]  )
        {
           prods[i] *= pi   ;  prods[j] *= pj   ;
        }
        return prods;
    }
    public static void main(String[] args) {
        System.out.println(
            Arrays.toString(products(1, 2, 3, 4, 5))
        ); // prints "[120, 60, 40, 30, 24]"
    }
}

Les invariants de boucle sont pi = nums[0] * nums[1] *.. nums[i-1]et pj = nums[N-1] * nums[N-2] *.. nums[j+1]. La ipartie de gauche est la logique «préfixe» et la jpartie de droite est la logique de «suffixe».


Une ligne récursive

Jasmeet a donné une (belle!) Solution récursive; Je l'ai transformé en ce (hideux!) One-liner Java. Il effectue des modifications sur place , avec O(N)un espace temporaire dans la pile.

static int multiply(int[] nums, int p, int n) {
    return (n == nums.length) ? 1
      : nums[n] * (p = multiply(nums, nums[n] * (nums[n] = p), n + 1))
          + 0*(nums[n] *= p);
}

int[] arr = {1,2,3,4,5};
multiply(arr, 1, 0);
System.out.println(Arrays.toString(arr));
// prints "[120, 60, 40, 30, 24]"

3
Je pense que la boucle à 2 variables rend plus difficile à comprendre que nécessaire (du moins pour mon pauvre cerveau!), Deux boucles séparées feraient également l'affaire.
Guillaume

C'est pourquoi j'ai séparé le code en gauche / droite, dans le but de montrer que les deux sont indépendants l'un de l'autre. Je ne sais pas si cela fonctionne réellement, cependant =)
polygenelubricants

15

Traduire la solution de Michael Anderson en Haskell:

otherProducts xs = zipWith (*) below above

     where below = scanl (*) 1 $ init xs

           above = tail $ scanr (*) 1 xs

13

Contournement sournois de la règle du "pas de division":

sum = 0.0
for i in range(a):
  sum += log(a[i])

for i in range(a):
  output[i] = exp(sum - log(a[i]))

2
Nitpick: pour autant que je sache, les ordinateurs implémentent des logarithmes en utilisant leur expansion binomiale - ce qui nécessite une division ...

10

Voilà, solution simple et propre avec une complexité O (N):

int[] a = {1,2,3,4,5};
    int[] r = new int[a.length];
    int x = 1;
    r[0] = 1;
    for (int i=1;i<a.length;i++){
        r[i]=r[i-1]*a[i-1];
    }
    for (int i=a.length-1;i>0;i--){
        x=x*a[i];
        r[i-1]=x*r[i-1];
    }
    for (int i=0;i<r.length;i++){
        System.out.println(r[i]);
    }

6

C ++, O (n):

long long prod = accumulate(in.begin(), in.end(), 1LL, multiplies<int>());
transform(in.begin(), in.end(), back_inserter(res),
          bind1st(divides<long long>(), prod));

9
la division n'est pas autorisée
Michael Anderson

C'est toujours un code génial, cependant. Avec l'avertissement qu'il utilise la division, je voterais quand même si une explication était donnée.
polygenelubricants

Merde, je n'ai pas lu la question. : s @polygenelubricants explication: l'idée est de le faire en deux étapes. Prenons d'abord la factorielle de la première séquence de nombres. C'est ce que fait l'algorithme d'accumulation (par défaut ajoute des nombres, mais peut prendre n'importe quelle autre opération binaire pour remplacer l'addition, dans ce cas une multiplication). Ensuite, j'ai parcouru la séquence d'entrée une deuxième fois, en la transformant de telle sorte que l'élément correspondant dans la séquence de sortie le factoriel I calculé à l'étape précédente divisé par l'élément correspondant dans la séquence d'entrée.
wilhelmtell

1
"factorielle de la première séquence"? wtf? je voulais dire le produit des éléments de séquence.
wilhelmtell

5
  1. Voyagez à gauche -> à droite et continuez à enregistrer le produit. Appelez-le passé. -> O (n)
  2. Déplacement vers la droite -> gauche pour conserver le produit. Appelez-le Future. -> O (n)
  3. Résultat [i] = Passé [i-1] * futur [i + 1] -> O (n)
  4. Passé [-1] = 1; et Future [n + 1] = 1;

Sur)


3

Voici ma solution en C ++ moderne. Il utilise std::transformet est assez facile à retenir.

Code en ligne (wandbox).

#include<algorithm>
#include<iostream>
#include<vector>

using namespace std;

vector<int>& multiply_up(vector<int>& v){
    v.insert(v.begin(),1);
    transform(v.begin()+1, v.end()
             ,v.begin()
             ,v.begin()+1
             ,[](auto const& a, auto const& b) { return b*a; }
             );
    v.pop_back();
    return v;
}

int main() {
    vector<int> v = {1,2,3,4,5};
    auto vr = v;

    reverse(vr.begin(),vr.end());
    multiply_up(v);
    multiply_up(vr);
    reverse(vr.begin(),vr.end());

    transform(v.begin(),v.end()
             ,vr.begin()
             ,v.begin()
             ,[](auto const& a, auto const& b) { return b*a; }
             );

    for(auto& i: v) cout << i << " "; 
}

2

C'est O (n ^ 2) mais f # est tellement beau:

List.fold (fun seed i -> List.mapi (fun j x -> if i=j+1 then x else x*i) seed) 
          [1;1;1;1;1]
          [1..5]

Je ne suis pas sûr qu'un énorme one liner ou une solution O (n ^ 2) à un problème O (n) soit jamais "beau".
Mad Physicist

2

Précalculez le produit des nombres à gauche et à droite de chaque élément. Pour chaque élément, la valeur souhaitée est le produit des produits de ses voisins.

#include <stdio.h>

unsigned array[5] = { 1,2,3,4,5};

int main(void)
{
unsigned idx;

unsigned left[5]
        , right[5];
left[0] = 1;
right[4] = 1;

        /* calculate products of numbers to the left of [idx] */
for (idx=1; idx < 5; idx++) {
        left[idx] = left[idx-1] * array[idx-1];
        }

        /* calculate products of numbers to the right of [idx] */
for (idx=4; idx-- > 0; ) {
        right[idx] = right[idx+1] * array[idx+1];
        }

for (idx=0; idx <5 ; idx++) {
        printf("[%u] Product(%u*%u) = %u\n"
                , idx, left[idx] , right[idx]  , left[idx] * right[idx]  );
        }

return 0;
}

Résultat:

$ ./a.out
[0] Product(1*120) = 120
[1] Product(1*60) = 60
[2] Product(2*20) = 40
[3] Product(6*5) = 30
[4] Product(24*1) = 24

(MISE À JOUR: maintenant je regarde de plus près, cela utilise la même méthode que Michael Anderson, Daniel Migowski et les lubrifiants polygéniques ci-dessus)


Quel est le nom de cet algorithme?
pièce du

1

Rusé:

Utilisez le suivant:

public int[] calc(int[] params) {

int[] left = new int[n-1]
in[] right = new int[n-1]

int fac1 = 1;
int fac2 = 1;
for( int i=0; i<n; i++ ) {
    fac1 = fac1 * params[i];
    fac2 = fac2 * params[n-i];
    left[i] = fac1;
    right[i] = fac2; 
}
fac = 1;

int[] results = new int[n];
for( int i=0; i<n; i++ ) {
    results[i] = left[i] * right[i];
}

Oui, je suis sûr que j'ai manqué un i-1 au lieu d'un i, mais c'est la façon de le résoudre.


1

Il existe également une solution non optimale O (N ^ (3/2)) . C'est tout à fait intéressant, cependant.

Tout d'abord prétraitez chaque multiplication partielle de taille N ^ 0,5 (ceci est fait en complexité temporelle O (N)). Ensuite, le calcul pour le multiple des autres valeurs de chaque nombre peut être effectué en 2 * O (N ^ 0,5) temps (pourquoi? Parce que vous n'avez besoin de multiplier que les derniers éléments des autres ((N ^ 0,5) - 1) nombres, et multipliez le résultat par ((N ^ 0.5) - 1) nombres qui appartiennent au groupe du nombre courant). En faisant cela pour chaque nombre, on peut obtenir le temps O (N ^ (3/2)).

Exemple:

4 6 7 2 3 1 9 5 8

résultats partiels: 4 * 6 * 7 = 168 2 * 3 * 1 = 6 9 * 5 * 8 = 360

Pour calculer la valeur de 3, il faut multiplier les valeurs des autres groupes 168 * 360, puis par 2 * 1.


1
public static void main(String[] args) {
    int[] arr = { 1, 2, 3, 4, 5 };
    int[] result = { 1, 1, 1, 1, 1 };
    for (int i = 0; i < arr.length; i++) {
        for (int j = 0; j < i; j++) {
            result[i] *= arr[j];

        }
        for (int k = arr.length - 1; k > i; k--) {
            result[i] *= arr[k];
        }
    }
    for (int i : result) {
        System.out.println(i);
    }
}

Cette solution que j'ai trouvée et je l'ai trouvée si claire que pensez-vous !?


1
Votre solution semble avoir une complexité temporelle O (n ^ 2).
Mad Physicist

1
def productify(arr, prod, i):
    if i < len(arr):
            prod.append(arr[i - 1] * prod[i - 1]) if i > 0 else prod.append(1)
            retval = productify(arr, prod, i + 1)
            prod[i] *= retval
            return retval * arr[i]
    return 1

arr = [1, 2, 3, 4, 5] prod = [] productify (arr, prod, 0) prod impression


1

Pour être complet voici le code en Scala:

val list1 = List(1, 2, 3, 4, 5)
for (elem <- list1) println(list1.filter(_ != elem) reduceLeft(_*_))

Cela imprimera ce qui suit:

120
60
40
30
24

Le programme filtrera l'élément actuel (_! = Elem); et multipliez la nouvelle liste avec la méthode reductionLeft. Je pense que ce sera O (n) si vous utilisez la vue scala ou Iterator pour l'évaluation paresseuse.


Bien qu'il soit très élégant, cela ne fonctionne pas s'il y a plus d'éléments avec la même valeur: val list1 = List (1, 7, 3, 3, 4, 4)
Giordano Scalzo

J'ai testé à nouveau le code avec des valeurs répétées. Il produit ce qui suit 1008 144 112 112 63 63 Je pense que c'est correct pour l'élément donné.
Billz

1

Basé sur la réponse de Billz - désolé, je ne peux pas commenter, mais voici une version scala qui gère correctement les éléments en double dans la liste, et est probablement O (n):

val list1 = List(1, 7, 3, 3, 4, 4)
val view = list1.view.zipWithIndex map { x => list1.view.patch(x._2, Nil, 1).reduceLeft(_*_)}
view.force

Retour:

List(1008, 144, 336, 336, 252, 252)

1

Ajout de ma solution javascript ici car je n'ai trouvé personne suggérant cela. Qu'est-ce que diviser, sauf pour compter le nombre de fois où vous pouvez extraire un nombre d'un autre nombre? J'ai calculé le produit du tableau entier, puis j'ai parcouru chaque élément et j'ai soustrait l'élément actuel jusqu'à zéro:

//No division operation allowed
// keep substracting divisor from dividend, until dividend is zero or less than divisor
function calculateProducsExceptCurrent_NoDivision(input){
  var res = [];
  var totalProduct = 1;
  //calculate the total product
  for(var i = 0; i < input.length; i++){
    totalProduct = totalProduct * input[i];
  }
  //populate the result array by "dividing" each value
  for(var i = 0; i < input.length; i++){
    var timesSubstracted = 0;
    var divisor = input[i];
    var dividend = totalProduct;
    while(divisor <= dividend){
      dividend = dividend - divisor;
      timesSubstracted++;
    }
    res.push(timesSubstracted);
  }
  return res;
}

1

Je suis habitué à C #:

    public int[] ProductExceptSelf(int[] nums)
    {
        int[] returnArray = new int[nums.Length];
        List<int> auxList = new List<int>();
        int multTotal = 0;

        // If no zeros are contained in the array you only have to calculate it once
        if(!nums.Contains(0))
        {
            multTotal = nums.ToList().Aggregate((a, b) => a * b);

            for (int i = 0; i < nums.Length; i++)
            {
                returnArray[i] = multTotal / nums[i];
            }
        }
        else
        {
            for (int i = 0; i < nums.Length; i++)
            {
                auxList = nums.ToList();
                auxList.RemoveAt(i);
                if (!auxList.Contains(0))
                {
                    returnArray[i] = auxList.Aggregate((a, b) => a * b);
                }
                else
                {
                    returnArray[i] = 0;
                }
            }
        }            

        return returnArray;
    }

1

Nous pouvons d'abord exclure le nums[j](where j != i) de la liste, puis obtenir le produit du reste; Ce qui suit est un python waypour résoudre ce puzzle:

from functools import reduce
def products(nums):
    return [ reduce(lambda x,y: x * y, nums[:i] + nums[i+1:]) for i in range(len(nums)) ]
print(products([1, 2, 3, 4, 5]))

[out]
[120, 60, 40, 30, 24]

0

Eh bien, cette solution peut être considérée comme celle du C / C ++. Disons que nous avons un tableau "a" contenant n éléments comme a [n], alors le pseudo-code serait comme ci-dessous.

for(j=0;j<n;j++)
  { 
    prod[j]=1;

    for (i=0;i<n;i++)
    {   
        if(i==j)
        continue;  
        else
        prod[j]=prod[j]*a[i];
  }

0

Encore une solution, en utilisant la division. avec double traversée. Multipliez tous les éléments, puis commencez à le diviser par chaque élément.


0
{-
Solution récursive utilisant des sous-ensembles sqrt (n). Fonctionne en O (n).

Calcule récursivement la solution sur des sous-ensembles sqrt (n) de taille sqrt (n). 
Puis revient sur la somme des produits de chaque sous-ensemble.
Ensuite, pour chaque élément de chaque sous-ensemble, il calcule le produit avec
la somme des produits de tous les autres produits.
Puis aplatit tous les sous-ensembles.

La récurrence à l'exécution est T (n) = sqrt (n) * T (sqrt (n)) + T (sqrt (n)) + n

Supposons que T (n) ≤ cn dans O (n).

T (n) = sqrt (n) * T (sqrt (n)) + T (sqrt (n)) + n
    ≤ sqrt (n) * c * sqrt (n) + c * sqrt (n) + n
    ≤ c * n + c * sqrt (n) + n
    ≤ (2c + 1) * n
    ∈ O (n)

Notez que le plafond (sqrt (n)) peut être calculé à l'aide d'une recherche binaire 
et O (logn) itérations, si l'instruction sqrt n'est pas autorisée.
-}

otherProducts [] = []
otherProducts [x] = [1]
otherProducts [x, y] = [y, x]
otherProducts a = foldl '(++) [] $ zipWith (\ sp -> map (* p) s) résoluSubsets subsetOtherProducts
    où 
      n = longueur a

      - Taille du sous-ensemble. Exiger que 1 <s <n.
      s = plafond $ sqrt $ fromIntegral n

      solvedSubsets = mapper d'autres sous-ensembles de produits
      subsetOtherProducts = otherProducts $ map sous-ensembles de produits

      subsets = reverse $ loop a []
          où boucle [] acc = acc
                boucle a acc = boucle (drop sa) ((take sa): acc)

0

Voici mon code:

int multiply(int a[],int n,int nextproduct,int i)
{
    int prevproduct=1;
    if(i>=n)
        return prevproduct;
    prevproduct=multiply(a,n,nextproduct*a[i],i+1);
    printf(" i=%d > %d\n",i,prevproduct*nextproduct);
    return prevproduct*a[i];
}

int main()
{
    int a[]={2,4,1,3,5};
    multiply(a,5,1,0);
    return 0;
}

0

Voici un exemple légèrement fonctionnel, utilisant C #:

            Func<long>[] backwards = new Func<long>[input.Length];
            Func<long>[] forwards = new Func<long>[input.Length];

            for (int i = 0; i < input.Length; ++i)
            {
                var localIndex = i;
                backwards[i] = () => (localIndex > 0 ? backwards[localIndex - 1]() : 1) * input[localIndex];
                forwards[i] = () => (localIndex < input.Length - 1 ? forwards[localIndex + 1]() : 1) * input[localIndex];
            }

            var output = new long[input.Length];
            for (int i = 0; i < input.Length; ++i)
            {
                if (0 == i)
                {
                    output[i] = forwards[i + 1]();
                }
                else if (input.Length - 1 == i)
                {
                    output[i] = backwards[i - 1]();
                }
                else
                {
                    output[i] = forwards[i + 1]() * backwards[i - 1]();
                }
            }

Je ne suis pas tout à fait certain que ce soit O (n), en raison de la semi-récursivité des Funcs créés, mais mes tests semblent indiquer que c'est O (n) dans le temps.


0

// Ceci est la solution récursive en Java // Appelé comme suite du produit principal (a, 1,0);

public static double product(double[] a, double fwdprod, int index){
    double revprod = 1;
    if (index < a.length){
        revprod = product2(a, fwdprod*a[index], index+1);
        double cur = a[index];
        a[index] = fwdprod * revprod;
        revprod *= cur;
    }
    return revprod;
}

0

Une solution soignée avec le runtime O (n):

  1. Pour chaque élément, calculez le produit de tous les éléments qui se produisent avant cela et stockez-le dans un tableau "pré".
  2. Pour chaque élément, calculez le produit de tous les éléments qui se produisent après cet élément et stockez-le dans un tableau "post"
  3. Créer un tableau final "résultat", pour un élément i,

    result[i] = pre[i-1]*post[i+1];
    

1
C'est la même solution que celle acceptée, non?
Thomas Ahle

0
function solution($array)
{
    $result = [];
    foreach($array as $key => $value){
        $copyOfOriginalArray = $array;
        unset($copyOfOriginalArray[$key]);
        $result[$key] = multiplyAllElemets($copyOfOriginalArray);
    }
    return $result;
}

/**
 * multiplies all elements of array
 * @param $array
 * @return int
 */
function multiplyAllElemets($array){
    $result = 1;
    foreach($array as $element){
        $result *= $element;
    }
    return $result;
}

$array = [1, 9, 2, 7];

print_r(solution($array));

0

Voici un autre concept simple qui résout le problème en O(N).

        int[] arr = new int[] {1, 2, 3, 4, 5};
        int[] outArray = new int[arr.length]; 
        for(int i=0;i<arr.length;i++){
            int res=Arrays.stream(arr).reduce(1, (a, b) -> a * b);
            outArray[i] = res/arr[i];
        }
        System.out.println(Arrays.toString(outArray));

0

J'ai une solution avec une complexité O(n)spatiale et O(n^2)temporelle fournie ci-dessous,

public static int[] findEachElementAsProduct1(final int[] arr) {

        int len = arr.length;

//        int[] product = new int[len];
//        Arrays.fill(product, 1);

        int[] product = IntStream.generate(() -> 1).limit(len).toArray();


        for (int i = 0; i < len; i++) {

            for (int j = 0; j < len; j++) {

                if (i == j) {
                    continue;
                }

                product[i] *= arr[j];
            }
        }

        return product;
    }
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.