ChatGPT解决这个技术问题 Extra ChatGPT

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

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

给定一个数字数组 nums,返回一个数字数组 products,其中 products[i] 是所有 nums[j] 的乘积,j != i。输入:[1, 2, 3, 4, 5] 输出:[(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) 中执行此操作而不使用除法。

这个问题在过去一周左右出现了几次;你们都是在同一家公司面试吗? :)
我目前正在浏览 [interview-questions] 标记寻找它。如果你找到了,有链接吗?
@Michael:这个问题允许分裂。我的明确禁止它。我会说这是两个不同的问题。
用 log(a/b)=log(a)-log(b) 代替除法,瞧!
想象一下,如果数组中有 1 个或多于 1 个零,您将如何处理这种情况?

b
blacktide

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) 中完成。

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

我的代码看起来像这样:

int a[N] // This is the input
int products_below[N];
int 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
int p = 1;
for (int i = 0; i < N; ++i) {
    products[i] = p;
    p *= a[i];
}

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

这是 O(n) 运行时,但在空间复杂度上也是 O(n)。您可以在 O(1) 空间内完成。我的意思是,除了输入和输出容器的大小之外。
非常聪明!这个算法有名字吗?
@MichaelAnderson 伟大的工作人员,但是请告诉我这背后的主要逻辑以及一旦你得到要求你是如何开始的。
如果任何一个元素为 0,算法将失败。所以不要忘记检查 0 以跳过。
@Mani如果元素设置为0,则该算法很好。但是,可以扫描输入以查找此类元素,如果找到它们,效率会更高。如果有两个零元素,则整个结果为零,如果只有一个,例如 v_i=0,则结果中唯一的非零条目是第 i 个元素。但是我怀疑添加一个通道来检测和计算零元素会降低解决方案的清晰度,并且在大多数情况下可能不会带来任何真正的性能提升。
b
blacktide

这是一个小的递归函数(在 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 它首先进行递归,记住中间产品,最终形成 num[N-1] 的数字产品;然后在返回的路上,它计算乘法的第二部分,然后用于修改数字数组。
想象一下,如果数组中有 1 个或多于 1 个零,您将如何处理这种情况?
C
Community

这是我尝试用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]"

我认为 2-variables 循环使它比必要的更难理解(至少对于我可怜的大脑来说!),两个单独的循环也可以完成这项工作。
这就是为什么我将代码分成左/右,以表明两者是相互独立的。不过,我不确定这是否真的有效=)
the code has a lot of duplication 不。该问题具有显着的对称性,通过您的方法和格式突出显示。
s
smac89

将 Michael Anderson 的解决方案翻译成 Haskell:

otherProducts xs = zipWith (*) below above

     where below = scanl (*) 1 $ init xs

           above = tail $ scanr (*) 1 xs

s
smac89

偷偷规避“不分割”规则:

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

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

Nitpick:据我所知,计算机使用二项式展开来实现对数 - 这确实需要除法......
r
raoadnan

在这里,简单而干净的解决方案具有 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]);
    }

你愿意写解释吗?如果代码乍看之下有意义,它可以为您赢得更多选票。解释科学。
u
user3177227

左转->右转并继续保存产品。称之为过去。 -> O(n) 向右旅行 -> 向左保持产品。称之为未来。 -> O(n) 结果[i] = Past[i-1] * future[i+1] -> O(n) Past[-1] = 1;和未来[n+1]=1;

上)


加一,专注于我们学习科学。
w
wilhelmtell

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

不过,这仍然是一个很棒的代码。免责声明它使用除法,如果给出解释,我仍然会赞成。
该死的,我没有读完这个问题。 :s @polygenelubricants 解释:这个想法是分两步完成。首先取第一个数字序列的阶乘。这就是累加算法的作用(默认情况下添加数字,但可以采用任何其他二进制运算来代替加法,在这种情况下是乘法)。接下来,我第二次迭代输入序列,将其转换为输出序列中的对应元素是我在上一步中计算的阶乘除以输入序列中的对应元素。
“第一个序列的阶乘”?什么?我的意思是序列元素的乘积。
P
Paul R

这是我在现代 C++ 中的解决方案。它利用了 std::transform 并且很容易记住。

Online code (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 << " "; 
}

w
wildplasser

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

#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

(更新:现在我仔细观察,这使用与上面的迈克尔安德森、丹尼尔 Migowski 和多基因润滑剂相同的方法)


这个算法叫什么名字?
D
Daniel Migowski

棘手:

使用以下内容:

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


L
Lars

这是 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) 解决方案是否永远“美丽”。
k
kolistivra

还有一个 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。


K
Kareem
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);
    }
}

我想出了这个解决方案,我发现它很清楚你的想法!?


您的解决方案似乎具有 O(n^2) 时间复杂度。
j
junkgui

基于 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)

s
soulus

在这里添加我的 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;
}

g
greybeard
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

if __name__ == "__main__":
    arr = [1, 2, 3, 4, 5]
    prod = []
    productify(arr, prod, 0)
    print(prod)

V
Vijay

好吧,这个解决方案可以被认为是 C/C++ 的解决方案。假设我们有一个包含 n 个元素的数组“a”,例如 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];
  }

这需要 O(n^2) 时间。
A
Alam

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


F
Fysx
{-
Recursive solution using sqrt(n) subsets. Runs in O(n).

Recursively computes the solution on sqrt(n) subsets of size sqrt(n). 
Then recurses on the product sum of each subset.
Then for each element in each subset, it computes the product with
the product sum of all other products.
Then flattens all subsets.

Recurrence on the run time is T(n) = sqrt(n)*T(sqrt(n)) + T(sqrt(n)) + n

Suppose that 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)

Note that ceiling(sqrt(n)) can be computed using a binary search 
and O(logn) iterations, if the sqrt instruction is not permitted.
-}

otherProducts [] = []
otherProducts [x] = [1]
otherProducts [x,y] = [y,x]
otherProducts a = foldl' (++) [] $ zipWith (\s p -> map (*p) s) solvedSubsets subsetOtherProducts
    where 
      n = length a

      -- Subset size. Require that 1 < s < n.
      s = ceiling $ sqrt $ fromIntegral n

      solvedSubsets = map otherProducts subsets
      subsetOtherProducts = otherProducts $ map product subsets

      subsets = reverse $ loop a []
          where loop [] acc = acc
                loop a acc = loop (drop s a) ((take s a):acc)

A
Anantha Krishnan

这是我的代码:

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

I
Ian Newson

这是一个使用 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)。


B
Billz

在这里要完整的是 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 我认为它对于给定的元素是正确的。
这需要 O(n^2) 时间。
u
user3552947

// 这是 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;
}

R
Russia Must Remove Putin

O(n) 运行时的简洁解决方案:

对于每个元素,计算之前出现的所有元素的乘积,并将其存储在数组“pre”中。对于每个元素,计算该元素之后出现的所有元素的乘积并将其存储在数组“post”中 创建最终数组“result”,对于元素 i,result[i] = pre[i-1]*post [我+1];


这与公认的解决方案相同,对吗?
S
SiddP

这是解决 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));

R
R.F

这是ptyhon版本

  # This solution use O(n) time and O(n) space
  def productExceptSelf(self, nums):
    """
    :type nums: List[int]
    :rtype: List[int]
    """
    N = len(nums)
    if N == 0: return

    # Initialzie list of 1, size N
    l_prods, r_prods = [1]*N, [1]*N

    for i in range(1, N):
      l_prods[i] = l_prods[i-1] * nums[i-1]

    for i in reversed(range(N-1)):
      r_prods[i] = r_prods[i+1] * nums[i+1]

    result = [x*y for x,y in zip(l_prods,r_prods)]
    return result

  # This solution use O(n) time and O(1) space
  def productExceptSelfSpaceOptimized(self, nums):
    """
    :type nums: List[int]
    :rtype: List[int]
    """
    N = len(nums)
    if N == 0: return

    # Initialzie list of 1, size N
    result = [1]*N

    for i in range(1, N):
      result[i] = result[i-1] * nums[i-1]

    r_prod = 1
    for i in reversed(range(N)):
      result[i] *= r_prod
      r_prod *= nums[i]

    return result

在您的答案中添加一些描述。这将比仅仅一段代码更有帮助。
R
RodrigoCampos

我习惯于 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;
    }

这需要 O(n^2) 时间。
p
prithwin

这是线性 O(n) 时间的简单 Scala 版本:

def getProductEff(in:Seq[Int]):Seq[Int] = {

   //create a list which has product of every element to the left of this element
   val fromLeft = in.foldLeft((1, Seq.empty[Int]))((ac, i) => (i * ac._1, ac._2 :+ ac._1))._2

   //create a list which has product of every element to the right of this element, which is the same as the previous step but in reverse
   val fromRight = in.reverse.foldLeft((1,Seq.empty[Int]))((ac,i) => (i * ac._1,ac._2 :+ ac._1))._2.reverse

   //merge the two list by product at index
   in.indices.map(i => fromLeft(i) * fromRight(i))

}

这是有效的,因为基本上答案是一个数组,它具有左右所有元素的乘积。


P
Pratik Patil
import java.util.Arrays;

public class Pratik
{
    public static void main(String[] args)
    {
        int[] array = {2, 3, 4, 5, 6};      //  OUTPUT: 360  240  180  144  120
        int[] products = new int[array.length];
        arrayProduct(array, products);
        System.out.println(Arrays.toString(products));
    }

    public static void arrayProduct(int array[], int products[])
    {
        double sum = 0, EPSILON = 1e-9;

        for(int i = 0; i < array.length; i++)
            sum += Math.log(array[i]);

        for(int i = 0; i < array.length; i++)
            products[i] = (int) (EPSILON + Math.exp(sum - Math.log(array[i])));
    }
}

输出:

[360, 240, 180, 144, 120]

时间复杂度:O(n) 空间复杂度:O(1)