【问题标题】:Interview question on problem solving (arrays)关于解决问题的面试题(数组)
【发布时间】:2011-09-22 23:30:48
【问题描述】:

有一个整数数组,比如说 3,5,7,9。您应该创建另一个数组并填充它,以便第二个数组的第 0 个位置应该是第一个数组中所有数字的乘积,不包括第 0 个位置的数字,这意味着它应该是 5x7x9(不包括 3),索引处的数字第二个数组的 1 将是 3x7x9 的乘积(不包括 5)。

我想到的第一个答案是有 2 个 for 循环,这将导致 O(n2) 的时间复杂度。后来我想通了:

将第一个数组(3x5x7x9)中的所有数字相乘,在填充第二个数组时,我将这个乘积除以该位置的数字。如果我填充第 0 个位置,则除以 3,如果我填充第 1 个位置,则除以 5,依此类推。这会将复杂度从 O(n2) 降低到可能 O(2n)。

但是面试官说不允许除法。除了将不同的可能倍数存储在某种数据结构中并在填充时使用它之外,我想不出其他任何东西。我放弃了,但当被问及答案时,他说他将保持 2 个向前和向后倍数的数组。当被问及解决方案的空间复杂度问题时,他表示可以进行优化。

【问题讨论】:

  • 一个小小的技术问题:O(2n) 正好是 O(n)。
  • 也许他的意思是在某种意义上它是最优的,因为该问题的空间复杂度为 O(n),并且该解决方案添加了另一个 O(n),总共仍然是 O(n)
  • @Charlie 知道你的意思...他是说 O(2n) 与 O(3n) 相同与 O(1000000n) 与 O(n) 相同。
  • 问题是面试官的空间优化还是他的解决方案的工作方式?他的解决方案相当简单……
  • 我想我可以对此有所了解:面试官没有想到您更简单,更优雅的解决方案,因此不允许划分。他们是否也不允许在代码中进行划分,并且每个人都必须围绕这种任意限制进行编码?

标签: algorithm


【解决方案1】:

我不确定问题是关于空间还是关于解决方案本身。由于大家一直在提供解决方案,所以我认为他们没有理解面试官的解决方案,所以这里解释一下:

我们维护两个数组。第一个,原始数组的数字的运行乘积。所以i 处的元素将是原始数组中第一个i 元素的乘积(没有乳胶,但ith 条目的值是pi{j=0 to i} j)。第二个数组只是它的相反方向,所以ith 条目将具有值:pi{j=N-i to N} j

要构造所需的最终数组,我们只需沿着两个数组中的每一个运行并乘以条目。所以最终数组中的ith 值将是所有条目的乘积,即0i-1 乘以i+1 到@987654331 的乘积@,它是第一个数组的 i-1 条目和第二个数组的 i+1 条目的乘积。

总时空O(n)

【讨论】:

  • 这是我通过阅读您的解决方案所理解的:如果输入数组有 3、5、7、9。 firstarray 将有 3、15(3x5)、105(3x5x7)、945(3x5x7x9),secondarray 将有 945(3x5x7x9)、315(5x7x9)、63 (7x9), 9. 然后你得到 finalarray 其中 finalarray[i] = firstarray[i] * secondarray[i]?如果是这样,那么它并不能解决问题。我希望我理解你的解决方案是错误的。
  • @ChrisOdney:finalarray[i] = firstarray[i-1] * secondarray[i+1] (如 davin 的回答和我的回答)会好吗?如果没有,请详细说明:)
  • @Chris,就大局而言这是正确的,但您的索引要精确。我说的是i-1i+1,不是ii,但我也没有仔细检查。让我们用你的例子: original-array =[3,5,7,9] first-array =[3,15,105,945] second-array =[945,315,63,9] 所以 final-array =[1*315,3*63,15*9,945*1] (我假设越界索引值为 1)。这将是正确的解决方案。
  • 知道了,谢谢。谈到复杂性,AFAI 理解它像我的解决方案一样是 O(n),但唯一的区别是它不使用除法?
  • 但是空间复杂度更高。它使用了 2 个大小为 n 的额外数组。
【解决方案2】:
  1. 将元素 1 存储到 i 的乘积到数组 A 中,A[i] 是元素 1 到元素 i 的乘积;

  2. 将元素 i 到 n(输入的大小)的乘积存储到数组中 B,其中B[i]是元素i到元素n的乘积;

  3. 当需要 result[i] 时,使用 A[i-1]*B[i+1]。角落案例是 这样简单,只需使用 B[1] 和 A[n-2] (其中 A[n-1] 是最后一个 元素)。

【讨论】:

  • 谢谢。原谅我的天真,我第一次读的时候没看懂。
【解决方案3】:

我想我可以在 O(n log n) 内完成。

存储前半部分数字和后半部分数字的乘积。

同时存储第一季度、第二季度、第三季度和第四季度的产品。

同时存储第一个八分之一,第二个八分之一,...第八个八分之一的乘积。

等等。

您可以通过计算每对的乘积,然后计算每对 对的乘积,以此类推,从头开始构建它。

这里额外数据的总量是 O(n) 并且需要 O(n) 来计算(很容易显示)。

现在,要计算(比如说)元素 0 的输出,您可以取下半部分的乘积、第二季度的乘积(即第一季度的下半部分)、下半部分的乘积第一个八分之一等,并将它们相乘。有log n个这样的数字,所以这个操作是log n。

要计算元素 k 的乘积,请将 k 写入二进制并翻转其位。然后高位告诉你从哪一半开始;下一位告诉您接下来要使用剩余一半的哪一半;下一位告诉您接下来要使用剩余季度的哪一半;等等。

所以任何输出元素都可以在 log n 时间内计算出来。对 n 个输出元素中的每一个执行此操作,得到 O(n log n)。

[编辑]

当然,“前向和后向倍数”方法也有效。我想我应该更仔细地阅读这个问题。 :-)

[编辑 2]

至于面试官(和davin)的解决方案,你实际上并不需要构造一个额外的数组......假设你可以编辑输出数组中的值。

首先,构造输出数组 B,使得 B[i] = A[0]A[1]...*A[i-1] 和 B[0]=1。您可以逐步执行此操作,因此这是线性时间:

B[0] = 1;
for (i=1 ; i < n ; ++i) {
    B[i] = B[i-1] * A[i];
}

接下来,从 n-2 向后扫描 以计算答案,跟踪“迄今为止的产品”:

x = A[n-1];
for (j=n-2 ; j >= 0 ; ++j) {
    B[j] *= x;
    x *= A[j];
}

这解决了 O(n) 中的问题,而无需构建额外的数组。

【讨论】:

  • 当您说 nlogn 时,您是否也考虑过提出半数、四分之一、八位数所涉及的复杂性?
  • @ChrisOdney:你只需要 n/2+n/4+n/8+...=n-1 次操作。
【解决方案4】:

我相信他的意思是,给定一个数组 A={a_1, ..., a_n} 他会创建两个新数组:

F_A={a_1, a_1 * a_2, ..., a_1 * ... * a_n}

可以通过在数组上向前迭代时保持累积乘积来在线性时间内构建,并且

B_A={a_1 * ... * a_n, ..., a_n * a_(n-1), a_n}

可以通过在数组上反向迭代时保持累积乘积来在线性时间内构建。

现在,要在结果数组中填充索引 i,您只需将 F_A[i-1] 与 B_A[i+1] 相乘:

F_A[i-1] * B_A[i+1] = [a_1 * ... * a_(i-1)] * [a_(i+1) * ... * a_n]

【讨论】:

  • 您实际上可以存储一个累积数组来代替原始数组。
【解决方案5】:

那么放弃对数呢?不是除法而是减法?

但是如果你可以修改第一个数组,你可以做类似的事情

//pop arr2
r=1
for(i=len-1;i>=0;i--)
{
   r=r*arr1[i]
   arr2[i]=r
}

//modify arr1
r=1
for(i=0;i<len;i++)
{
   r=r*arr1[i]
   arr1[i]=r
}

//fix arr2
arr2[0]=arr2[1];
for(i=1;i<len-1;i--)
{
     arr2[i]=arr2[i+1]*arr1[i-1];
}
arr2[len-1]=arr1[len-2];

【讨论】:

    猜你喜欢
    • 2020-09-20
    • 2011-03-17
    • 1970-01-01
    • 2011-07-23
    • 2020-03-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多