给定一个数字数组,返回所有其他数字的乘积数组(无除法)


186

在工作面试中有人问我这个问题,我想知道其他人会如何解决。我对Java最满意,但是欢迎使用其他语言的解决方案。

给定一个数字数组nums,返回一个数字数组products,其中products[i]all的乘积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

我目前正在浏览[interview-questions]标签寻找它。找到链接了吗?
polygenelubricants 2010年

2
@迈克尔:这个问题允许分裂。矿山明确禁止这样做。我会说这是两个不同的问题。
polygenelubricants 2010年

8
用log(a / b)= log(a)-log(b)代替除法!
ldog

1
想象一下,如果数组中有1个或多于1个零,您将如何处理这种情况?
gst

Answers:


257

多基因润滑剂方法的解释是:诀窍是构造数组(对于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)中完成。

然后将两个数组元素相乘得到所需的结果

我的代码如下所示:

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'4

8
非常聪明!这个算法有名字吗?
fastcodejava

2
@MichaelAnderson太好了,但是,请告诉我这背后的主要逻辑,以及一旦获得要求,如何开始这项工作。
ACBalaji 2012年

3
如果任何一个元素为0,则算法将失败。因此,请不要忘记选中0以跳过。
玛尼2014年

2
@Mani如果将元素设置为0,该算法很好。但是,可以在输入中扫描此类元素,如果找到它们,则效率更高。如果有两个零元素,则整个结果为零,如果只有一个,则说v_i=0结果中唯一的非零条目是第ith个元素。但我怀疑,加入了一通检测和计数的零个元素会从溶液的澄清度损害,而且可能不会在大多数情况下,任何真正的性能增益..
迈克尔·安德森

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 2012年

1
@nikhil首先进行递归,记住中间乘积,最后形成的乘积num[N-1];然后在返回的途中,计算乘法的第二部分,然后将其用于修改数字数组。
杰克

想象一下,如果数组中有1个或多于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变量循环使理解变得不必要的困难(至少对于我的大脑不好!),两个单独的循环也可以完成任务。
Guillaume

这就是为什么我将代码分为左/右,以表明两者是相互独立的。我不确定这是否真的有效,尽管=)
polygenelubricants

15

将迈克尔·安德森的解决方案翻译为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:据我所知,计算机使用其二项式展开实现对数-这确实需要除法...

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
不允许分组表决
Michael Anderson 2010年

不过,那仍然是一个很棒的代码。由于它使用除法的免责声明,如果给出解释,我仍然会赞成。
polygenelubricants 2010年

该死的,我没看清楚问题。:s @polygenelubricants解释:想法是分两个步骤完成的。首先采用第一个数字序列的阶乘。这就是累加算法的功能(默认情况下会加上数字,但是可以采用任何其他二进制运算来代替加法,在这种情况下是乘法)。接下来,我第二次遍历输入序列,对其进行转换,以使输出序列中的相应元素乘上一步中计算的阶乘I除以输入序列中的相应元素。
wilhelmtell

1
“第一序列的阶乘”?wtf?我的意思是序列元素的乘积。
wilhelmtell

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并且很容易记住。

在线代码(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

这是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)问题的O(n ^ 2)解决方案是否“美丽”。
疯狂物理学家

2

预先计算每个元素左右两侧的数字乘积。对于每个元素,期望值都是其neigbors产品的乘积。

#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

(更新:现在我仔细看,它使用与上面的迈克尔·安德森,丹尼尔·米格夫斯基和多基因润滑剂相同的方法)


该算法的名称是什么?
onepiece

1

整rick:

使用以下内容:

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-1而不是i,但这就是解决它的方法。


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)打印产品


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方法相乘。我认为如果使用scala视图或Iterator进行惰性评估,则将为O(n)。


尽管非常优雅,但是如果有更多元素具有相同的值,它将不起作用:val list1 = List(
1、7、3、3、4、4

我用重复的值再次测试了代码。它产生下面的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个元素,例如a [n],那么伪代码将如下所示。

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

另一种解决方案,使用除法。两次遍历。将所有元素相乘,然后开始将其除以每个元素。


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)迭代。
-}

其他产品[] = []
其他产品[x] = [1]
其他产品[x,y] = [y,x]
otherProducts a = foldl'(++)[] $ zipWith(\ sp-> map(* p)s)resolveSubsets子集OtherProducts
    哪里 
      n =长度a

      -子集大小。要求1 <s <n。
      s =上限$ sqrt $ from积分n

      resolveSubsets =映射其他产品子集
      subsetOtherProducts = otherProducts $地图产品子集

      子集=反向$循环a []
          其中loop [] acc = acc
                循环acc =循环(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中的递归解决方案//从主要乘积(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
这和公认的解决方案是一样的,对吗?
Thomas Ahle 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;
    }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.