Data una matrice di numeri, restituisce una matrice di prodotti di tutti gli altri numeri (nessuna divisione)


186

Mi è stata posta questa domanda in un colloquio di lavoro e vorrei sapere come gli altri lo avrebbero risolto. Mi sento più a mio agio con Java, ma le soluzioni in altre lingue sono benvenute.

Dato un array di numeri, numsrestituisce un array di numeri products, dove products[i]è il prodotto di tutti 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]

Devi farlo O(N)senza usare la divisione.


49
Questa domanda è emersa alcune volte nell'ultima settimana circa; state intervistando tutti con la stessa compagnia? :)
Michael Mrozek,

Sto attualmente sfogliando il [interview-questions]tag cercandolo. Hai un link se l'hai trovato?
poligenelubrificanti

2
@Michael: questa domanda consente la divisione. Il mio lo proibisce esplicitamente. Direi che sono due domande diverse.
poligenilubrificanti

8
Sostituisci divisione con log (a / b) = log (a) -log (b) e voilà!
Cane

1
immagina se ci sono 1 o più di 1 zeri nell'array, come gestirai il caso ??
GST

Risposte:


257

Una spiegazione del metodo dei poligenelubrificanti è: il trucco è costruire gli array (nel caso di 4 elementi)

{              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,  }

Entrambi possono essere fatti in O (n) iniziando rispettivamente dai bordi sinistro e destro.

Quindi moltiplicando i due array elemento per elemento si ottiene il risultato richiesto

Il mio codice sarebbe simile a questo:

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];
}

Se hai bisogno di essere O (1) anche nello spazio puoi farlo (che è meno chiaro IMHO)

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
Questo è O (n) runtime ma è anche O (n) nella complessità dello spazio. Puoi farlo nello spazio O (1). Voglio dire, oltre alle dimensioni dei contenitori di input e output ovviamente.
Wilhelmtell,

8
Molto intelligente! C'è un nome per questo algoritmo?
fastcodejava,

2
@MichaelAnderson Ottimo lavoro uomo, ma per favore dimmi la logica principale dietro questo e come hai iniziato questo una volta ottenuto il requisito.
ACBalaji,

3
L'algoritmo fallirà se uno qualsiasi degli elementi è 0. Quindi non dimenticare di controllare lo 0 per saltare.
Mani

2
@Mani L'algoritmo va bene se ci sono elementi impostati su 0. Tuttavia potrebbe essere possibile scansionare l'input alla ricerca di tali elementi ed essere più efficiente se vengono trovati. Se ci sono due elementi zero, l'intero risultato è zero e se ce n'è solo uno, supponiamo che v_i=0l'unica voce diversa da zero nel risultato sia l'ith element. Tuttavia, ho il sospetto che l'aggiunta di un passaggio per rilevare e contare gli elementi zero pregiudicherebbe la chiarezza della soluzione e probabilmente non realizzerebbe alcun reale miglioramento delle prestazioni nella maggior parte dei casi.
Michael Anderson,

52

Ecco una piccola funzione ricorsiva (in C ++) per eseguire la modofication in atto. Tuttavia richiede O (n) spazio extra (in pila). Supponendo che l'array sia in a e N detenga la lunghezza dell'array, abbiamo

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;
}

Qualcuno potrebbe spiegare questa ricorsione?
nikhil,

1
@nikhil Fa prima la ricorsione, ricordando i prodotti intermedi, formando infine il numero del prodotto per num[N-1]; poi sulla via del ritorno calcola la seconda parte della moltiplicazione che viene quindi utilizzata per modificare l'array numerico in atto.
Ja͢ck,

immagina se ci sono 1 o più di 1 zeri nell'array, come gestirai il caso ??
GST

18

Ecco il mio tentativo di risolverlo in Java. Mi scuso per la formattazione non standard, ma il codice ha molte duplicazioni e questo è il massimo che posso fare per renderlo leggibile.

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]"
    }
}

Gli invarianti ad anello sono pi = nums[0] * nums[1] *.. nums[i-1]e pj = nums[N-1] * nums[N-2] *.. nums[j+1]. La iparte a sinistra è la logica "prefisso" e la jparte a destra è la logica "suffisso".


One-liner ricorsivo

Jasmeet ha dato una (bella!) Soluzione ricorsiva; L'ho trasformato in questo (orribile!) Java one-liner. Esegue modifiche sul posto , con O(N)spazio temporaneo nello stack.

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
Penso che il ciclo a 2 variabili renda più difficile da capire del necessario (almeno per il mio povero cervello!), Anche due circuiti separati farebbero il lavoro.
Guillaume,

Ecco perché ho separato il codice in sinistra / destra, nel tentativo di dimostrare che i due sono indipendenti l'uno dall'altro. Non sono sicuro che funzioni davvero, anche se =)
poligenilubrificanti

15

Traducendo la soluzione di Michael Anderson in Haskell:

otherProducts xs = zipWith (*) below above

     where below = scanl (*) 1 $ init xs

           above = tail $ scanr (*) 1 xs

13

Aggirando di soppiatto la regola "nessuna divisione":

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: per quanto io sappia, i computer implementare logaritmi usando il loro binomio di espansione - che fa richiede divisione ...

10

Ecco a voi una soluzione semplice e pulita con complessità 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
divisione non è consentita
Michael Anderson,

Questo è ancora un codice dall'aspetto fantastico, però. Con la dichiarazione di non responsabilità che utilizza la divisione, continuerei a votare se mi viene fornita una spiegazione.
poligenelubrificanti

Accidenti, non ho letto la domanda. : s @polygenelubricants spiegation: l'idea è di farlo in due passaggi. Per prima cosa prendi il fattoriale della prima sequenza di numeri. Questo è ciò che fa l'algoritmo di accumulo (per impostazione predefinita aggiunge numeri, ma può richiedere qualsiasi altra operazione binaria per sostituire l'aggiunta, in questo caso una moltiplicazione). Successivamente ho ripetuto una seconda volta la sequenza di input, trasformandola in modo tale che l'elemento corrispondente nella sequenza di output abbia calcolato il fattoriale nel passaggio precedente diviso per l'elemento corrispondente nella sequenza di input.
Wilhelmtell,

1
"fattoriale della prima sequenza"? WTF? intendevo il prodotto degli elementi della sequenza.
Wilhelmtell,

5
  1. Viaggia a sinistra-> destra e continua a salvare il prodotto. Chiamalo passato. -> O (n)
  2. Viaggia a destra -> sinistra per conservare il prodotto. Chiamalo futuro. -> O (n)
  3. Risultato [i] = Passato [i-1] * futuro [i + 1] -> O (n)
  4. Passato [-1] = 1; e futuro [n + 1] = 1;

Sopra)


3

Ecco la mia soluzione nel moderno C ++. Si avvale di std::transformed è abbastanza facile da ricordare.

Codice online (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

Questo è O (n ^ 2) ma f # è veramente bello:

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]

Non sono sicuro che un'enorme copertina o una soluzione O (n ^ 2) a un problema O (n) siano mai "belle".
Fisico pazzo,

2

Calcola preventivamente il prodotto dei numeri a sinistra e a destra di ciascun elemento. Per ogni elemento il valore desiderato è il prodotto dei suoi prodotti vicini.

#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;
}

Risultato:

$ ./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

(AGGIORNAMENTO: ora guardo più da vicino, questo utilizza lo stesso metodo di Michael Anderson, Daniel Migowski e i poligenelubrificanti sopra)


Qual è il nome di questo algoritmo?
pezzo

1

Difficile:

Utilizza il seguente:

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];
}

Sì, sono sicuro di aver perso alcuni i-1 anziché i, ma questo è il modo di risolverlo.


1

C'è anche una O (N ^ (3/2)) non ottimale soluzione . È abbastanza interessante, comunque.

Prima preelaborare ogni moltiplicazione parziale di dimensione N ^ 0,5 (ciò avviene nella complessità temporale O (N)). Quindi, il calcolo per il multiplo di altri valori di ciascun numero può essere eseguito in 2 * O (N ^ 0,5) tempo (perché? Perché devi solo moltiplicare gli ultimi elementi di altri ((N ^ 0,5) - 1), e moltiplica il risultato con ((N ^ 0,5) - 1) numeri che appartengono al gruppo del numero corrente). In questo modo per ogni numero, si può ottenere il tempo O (N ^ (3/2)).

Esempio:

4 6 7 2 3 1 9 5 8

risultati parziali: 4 * 6 * 7 = 168 2 * 3 * 1 = 6 9 * 5 * 8 = 360

Per calcolare il valore di 3, è necessario moltiplicare i valori degli altri gruppi 168 * 360, quindi con 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);
    }
}

Questa soluzione mi è venuta in mente e l'ho trovata così chiara cosa ne pensi !?


1
La soluzione sembra avere una complessità temporale O (n ^ 2).
Fisico pazzo,

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) stampa prod


1

Per essere completo ecco il codice in Scala:

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

Questo stamperà quanto segue:

120
60
40
30
24

Il programma filtrerà l'attuale elem (_! = Elem); e moltiplicare il nuovo elenco con il metodo reduceLeft. Penso che questo sarà O (n) se usi scala view o Iterator per il lazy eval.


Nonostante sia molto elegante, non funziona se ci sono più elementi con lo stesso valore: val list1 = List (1, 7, 3, 3, 4, 4)
Giordano Scalzo,

Ho testato nuovamente il codice con valori ripetuti. Produce il seguente 1008 144 112 112 63 63 Penso che sia corretto per l'elemento dato.
Billz,

1

Basato sulla risposta di Billz - scusa non posso commentare, ma ecco una versione di scala che gestisce correttamente gli elementi duplicati nell'elenco e probabilmente è 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

ritorna:

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

1

Aggiungendo la mia soluzione javascript qui perché non ho trovato nessuno che lo suggerisse. Cosa si divide, se non contare il numero di volte in cui è possibile estrarre un numero da un altro numero? Ho analizzato il prodotto dell'intero array, quindi ho ripetuto su ogni elemento e sottratto l'elemento corrente fino a zero:

//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

Sono abituato a 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

Possiamo prima escludere nums[j](dove j != i) dall'elenco, quindi ottenere il prodotto del resto; Di seguito è un python wayper risolvere questo 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

Bene, questa soluzione può essere considerata quella di C / C ++. Diciamo che abbiamo un array "a" contenente n elementi come un [n], quindi lo pseudo codice sarebbe come di seguito.

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

Un'altra soluzione, utilizzando la divisione. con due attraversamenti. Moltiplica tutti gli elementi e quindi inizia a dividerlo per ciascun elemento.


0
{-
Soluzione ricorsiva usando sottoinsiemi sqrt (n). Funziona in O (n).

Calcola ricorsivamente la soluzione su sottoinsiemi sqrt (n) di dimensione sqrt (n). 
Quindi recluta la somma del prodotto di ciascun sottoinsieme.
Quindi, per ciascun elemento in ciascun sottoinsieme, calcola il prodotto con
la somma dei prodotti di tutti gli altri prodotti.
Quindi appiattisce tutti i sottoinsiemi.

La ricorrenza sul tempo di esecuzione è T (n) = sqrt (n) * T (sqrt (n)) + T (sqrt (n)) + n

Supponiamo che T (n) ≤ cn in 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)

Si noti che il soffitto (sqrt (n)) può essere calcolato usando una ricerca binaria 
e O (logn) iterazioni, se l'istruzione sqrt non è consentita.
-}

otherProducts [] = []
otherProducts [x] = [1]
otherProducts [x, y] = [y, x]
otherProducts a = foldl '(++) [] $ zipWith (\ sp -> map (* p) s) risoltoSetset subsetAltriProdotti
    dove 
      n = lunghezza a

      - Dimensione del sottoinsieme. Richiede che 1 <s <n.
      s = massimale $ sqrt $ da Integrale n

      solvedSubsets = mappa sottoinsiemi di altri prodotti
      subsetOtherProducts = otherProducts $ map sottoinsiemi di prodotti

      subset = reverse $ loop a []
          dove loop [] acc = acc
                loop a acc = loop (drop sa) ((take sa): acc)

0

Ecco il mio codice:

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

Ecco un esempio leggermente funzionale, usando 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]();
                }
            }

Non sono del tutto sicuro che questo sia O (n), a causa della semi-ricorsione dei Func creati, ma i miei test sembrano indicare che è O (n) nel tempo.


0

// Questa è la soluzione ricorsiva in Java // Chiamata come segue dal prodotto principale (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

Una soluzione pulita con runtime O (n):

  1. Per ogni elemento calcola il prodotto di tutti gli elementi che si verificano prima e memorizza in un array "pre".
  2. Per ogni elemento calcolare il prodotto di tutti gli elementi che si verificano dopo quell'elemento e memorizzarlo in un array "post"
  3. Crea un "risultato" di array finale, per un elemento i,

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

1
Questa è la stessa soluzione di quella accettata, giusto?
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

Ecco un altro concetto semplice che risolve il problema 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

Ho una soluzione con complessità O(n)spazio- O(n^2)temporale fornita di seguito,

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;
    }
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.