数値の配列を指定すると、他のすべての数値の積の配列を返します(除算なし)


186

この質問は就職の面接で尋ねられました。他の人がそれをどのように解決するか知りたいのですが。私はJavaに最も慣れていますが、他の言語のソリューションも歓迎します。

数値の配列を指定するとnums、は数値の配列を返しますproducts。ここproducts[i]で、はすべての積です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]

O(N)除算を使用せずにこれを行う必要があります。


49
この質問は先週かそこらで何度か出てきました。みなさんは同じ会社にインタビューしていますか?:)
Michael Mrozek 2010

私は現在[interview-questions]それを探しているタグを閲覧しています。見つけたらリンクはありますか?
polygenelubricants 2010

2
@マイケル:その質問は分割を可能にします。鉱山はそれを明示的に禁止します。2つの異なる質問だと思います。
polygenelubricants 2010

8
部門をlog(a / b)= log(a)-log(b)に置き換えて、出来上がり!
ldog 2013

1
配列にゼロが1つ以上ある場合、どのようにケースを処理しますか?
gst

回答:


257

polygenelubricantsメソッドの説明は次のとおりです。トリックは配列を作成することです(4要素の場合)。

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

どちらも、左端と右端から開始することにより、O(n)で実行できます。

次に、2つの配列を要素ごとに乗算すると、必要な結果が得られます

私のコードは次のようになります:

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

空間でもO(1)になる必要がある場合は、これを行うことができます(これはあまり明確ではありません)

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
これはO(n)ランタイムですが、スペースの複雑さもO(n)です。O(1)空間で実行できます。もちろん、入力コンテナと出力コンテナのサイズ以外は。
wilhelmtell 2010

8
非常に賢い!このアルゴリズムの名前はありますか?
fastcodejava

2
@MichaelAnderson素晴らしい仕事人ですが、この背後にある主要なロジックと、要件を取得した後、どのようにこれを始めたのか教えてください。
ACBalaji 2012

3
要素のいずれかが0の場合、アルゴリズムは失敗します。スキップするために0を確認することを忘れないでください。
Mani

2
@Mani要素が0に設定されている場合、アルゴリズムは問題ありません。ただし、そのような要素の入力をスキャンして、見つかった場合はより効率的になる可能性があります。2つのゼロ要素がある場合、結果全体はゼロであり、1つだけある場合v_i=0、結果のゼロ以外の唯一のエントリはi番目の要素です。しかし、私はゼロ要素を検出してカウントするようにパスを追加すると溶液の透明性を損なう、おそらくほとんどの場合で任意の実際のパフォーマンスの向上をすることはないだろうと思われる...
マイケル・アンダーソン

52

これは、変更を適切に行うための小さな再帰関数(C ++)です。ただし、(スタック上に)O(n)の余分なスペースが必要です。配列がaにあり、Nが配列の長さを保持しているとすると、

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

誰かがこの再帰を説明できますか?
nikhil

1
@nikhilそれは最初に再帰を行い、中間生成物を思い出し、最終的には数の生成物を形成しnum[N-1]ます。次に、帰り道で、乗算の2番目の部分を計算します。これは、数値配列を変更するために使用されます。
ジャック

配列にゼロが1つ以上ある場合、どのようにケースを処理しますか?
gst

18

Javaでそれを解決する私の試みはここにあります。非標準の書式設定についてお詫びしますが、コードには多くの重複があり、これを読みやすくするために私ができる最善の方法です。

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

ループ不変式はpi = nums[0] * nums[1] *.. nums[i-1]およびpj = nums[N-1] * nums[N-2] *.. nums[j+1]です。i左側の部分は「接頭辞」ロジックでj、右側の部分は「接尾辞」ロジックです。


再帰的なワンライナー

Jasmeetは(美しい!)再帰的なソリューションを提供しました。私はそれをこの(恐ろしい!)Javaワンライナーに変えました。それはありませんインプレース変更をして、O(N)スタック内の一時的なスペース。

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
2変数ループは必要以上に理解が難しくなると思います(少なくとも私の頭の悪い人にとっては!)、2つの別個のループも同様に機能します。
Guillaume、

そのため、2つが互いに独立していることを示すために、コードを左/右に分けました。それが実際に機能するかどうかはわかりませんが、=)
polygenelubricants

15

Michael AndersonのソリューションをHaskellに翻訳する:

otherProducts xs = zipWith (*) below above

     where below = scanl (*) 1 $ init xs

           above = tail $ scanr (*) 1 xs

13

「分割なし」ルールをこっそり回避する:

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:私の知る限り、コンピュータは2項展開を使用して対数を実装してます

10

ここでは、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
分割は許可されていません
マイケルアンダーソン

ただし、それでも見栄えの良いコードです。除算を使用しているという免責事項があるため、説明があれば私はまだ賛成票を投じます。
polygenelubricants 2010

くそー、私は質問を読み通しませんでした。:s @polygenelubricants説明:アイデアは2つのステップで実行することです。最初に、最初の数列の階乗を取ります。これが、累算アルゴリズムが行うことです(デフォルトでは数値を加算しますが、加算の代わりに他のバイナリ演算を使用できます(この場合は乗算))。次に、入力シーケンスをもう一度繰り返し、出力シーケンスの対​​応する要素が前のステップで計算した階乗Iを入力シーケンスの対​​応する要素で割るように変換しました。
wilhelmtell 2010

1
「最初のシーケンスの階乗」?wtf?私はシーケンス要素の積を意味しました。
wilhelmtell 2010

5
  1. 左に移動->右に移動し、製品を保存し続けます。過去と呼んでください。-> O(n)
  2. 右に移動->左に製品を保持します。それを未来と呼んでください。-> O(n)
  3. 結果[i] =過去[i-1] *将来[i + 1]-> O(n)
  4. 過去[-1] = 1; およびFuture [n + 1] = 1;

オン)


3

これが最新のC ++での私の解決策です。それを利用し、std::transform覚えるのはかなり簡単です。

オンラインコード(ワンドボックス)。

#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

これはO(n ^ 2)ですが、f#はとても美しいです。

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]

O(n)の問題に対する巨大な1つのライナーまたはO(n ^ 2)の解決策が「美しい」かどうかはわかりません。
マッド物理学者、

2

各要素の左側と右側の数値の積を事前計算します。すべての要素について、望ましい値はその隣人の製品の積です。

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

結果:

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

(更新:私はもっとよく見て、これはマイケル・アンダーソン、ダニエル・ミゴウスキー、および上記のポリジェネルブリカントと同じ方法を使用しています)


このアルゴリズムの名前は何ですか?
ワンピース2016

1

トリッキー:

以下を使用します。

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

はい、私はiの代わりにいくつかのi-1を逃したと確信していますが、それはそれを解決する方法です。


1

O(N ^(3/2))非最適もあります解もあります。しかし、それは非常に興味深いものです。

まず、サイズN ^ 0.5の各部分乗算を前処理します(これはO(N)時間の複雑さで行われます)。次に、各数値の他の値の倍数の計算は、2 * O(N ^ 0.5)時間で実行できます(他の((N ^ 0.5)-1)数値の最後の要素を乗算するだけなので、結果に((N ^ 0.5)-1)現在の数値のグループに属する数値)を掛けます。これを各数値に対して行うと、O(N ^(3/2))時間を得ることができます。

例:

4 6 7 2 3 1 9 5 8

部分的な結果:4 * 6 * 7 = 168 2 * 3 * 1 = 6 9 * 5 * 8 = 360

3の値を計算するには、他のグループの値に168 * 360を掛け、次に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);
    }
}

私が思いついたこの解決策は、あなたがどう思いますか?


1
あなたのソリューションはO(n ^ 2)時間の複雑さを持っているようです。
マッド物理学者、

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


1

ここで完了するのは、Scalaのコードです。

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

これにより、以下が出力されます。

120
60
40
30
24

プログラムは現在のelem(_!= elem)を除外します。新しいリストにreduceLeftメソッドを掛けます。怠惰なevalにscalaビューまたはIteratorを使用する場合、これはO(n)になると思います。


非常にエレガントですが、同じ値を持つ要素が他にある場合は機能しません。vallist1 = List(
Giordano Scalzo 14年

繰り返し値を使用してコードを再度テストしました。次のような結果になります。1008 144 112 112 63 63与えられた要素に対して正しいと思います。
Billz 2014年

1

Billzの回答に基づいて-申し訳ありませんがコメントはできませんが、これはリスト内の重複する項目を正しく処理するscalaバージョンであり、おそらく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

戻り値:

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

1

私がこれを示唆している人を見つけられなかったので、ここに私のJavaScriptソリューションを追加します。別の数値から数値を抽出できる回数を数えることを除いて、除算とは何ですか?配列全体の積を計算し、各要素を繰り返し、現在の要素をゼロまで減算しました。

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

私は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

最初にnums[j](where j != i)をリストから除外してから、残りの積を取得できます。以下はpython wayこのパズルを解くためのものです:

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

さて、このソリューションはC / C ++のソリューションと考えることができます。a [n]のようなn個の要素を含む配列 "a"があるとすると、疑似コードは次のようになります。

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

もう1つの解決策は、部門の使用です。2回のトラバーサルで。すべての要素を乗算してから、それを各要素で除算し始めます。


0
{-
sqrt(n)サブセットを使用した再帰的なソリューション。O(n)で実行されます。

サイズsqrt(n)のsqrt(n)サブセットの解を再帰的に計算します。 
次に、各サブセットの積和を再帰します。
次に、各サブセットの各要素について、次の式で積を計算します。
他のすべての製品の製品合計。
次に、すべてのサブセットをフラット化します。

実行時の反復はT(n)= sqrt(n)* T(sqrt(n))+ T(sqrt(n))+ n

O(n)のT(n)≤cnと仮定します。

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)

ただし、ceiling(sqrt(n))はバイナリサーチを使用して計算できます。 
sqrt命令が許可されていない場合は、O(logn)反復。
-}

otherProducts [] = []
otherProducts [x] = [1]
otherProducts [x、y] = [y、x]
otherProducts a = foldl '(++)[] $ zipWith(\ sp-> map(* p)s)solveSubsetsサブセット
    どこ 
      n =長さa

      -サブセットサイズ。1 <s <nである必要があります。
      s = ceiling $ sqrt $ fromIntegral n

      solveSubsets = otherProductsサブセットをマップする
      subsystemOtherProducts = otherProducts $マップ製品のサブセット

      サブセット=逆$ループa []
          ここでループ[] acc = acc
                loop a acc = loop(drop sa)((take sa):acc)

0

これが私のコードです:

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

これは、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]();
                }
            }

作成されたFuncsの半再帰により、これがO(n)であるかどうかは完全にはわかりませんが、私のテストでは、O(n)が時間どおりであることを示しているようです。


0

//これはJavaの再帰的なソリューションです// main product(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

O(n)ランタイムを備えたきちんとしたソリューション:

  1. 各要素について、その前に出現するすべての要素の積を計算し、配列「pre」に格納します。
  2. 各要素について、その要素の後に発生するすべての要素の積を計算し、配列「post」に格納します
  3. 要素iの最終的な配列「結果」を作成します。

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

1
これは、受け入れられているソリューションと同じですよね?
トーマスアーレ2014

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

次に、の問題を解決する別の単純な概念を示し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

私は以下に提供されるO(n)空間とO(n^2)時間の複雑さを持つソリューションを持っています、

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;
    }
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.