前段时间我有一次有趣的面试经历。问题一开始很简单:

Q1:我们有一个袋子,里面有数字1,2,3,…,100。每个数字恰好出现一次,所以有100个数字。现在从袋子里随机抽取一个数字。找到丢失的号码。

当然,我以前听过这个面试问题,所以我很快就回答了这个问题:

A1:嗯,1 + 2 + 3 +…+ N的和是(N+1)(N/2)(参见维基百科:等差级数的和)。当N = 100时,和是5050。 因此,如果所有的数字都在袋子里,总和将恰好是5050。因为少了一个数,总和就会小于这个数,差的就是这个数。所以我们可以在O(N)时间和O(1)空间中找到这个缺失的数。

在这一点上,我认为我做得很好,但突然间,问题发生了意想不到的转变:

这是正确的,但是如果少了两个数字,你会怎么做?

我以前从未见过/听过/考虑过这种变化,所以我很恐慌,无法回答这个问题。面试官坚持要知道我的思考过程,所以我提到,也许我们可以通过与预期产品进行比较来获得更多信息,或者在从第一次传递中收集到一些信息后再进行第二次传递,等等,但我真的只是在黑暗中拍摄,而不是真正有一个明确的解决方案的路径。

面试官试图鼓励我说,有第二个方程确实是解决问题的一种方法。在这一点上,我有点不安(因为事先不知道答案),并问这是一种通用的(阅读:“有用的”)编程技术,还是只是一个技巧/答案。

面试官的回答让我惊讶:你可以把这个技巧概括为3个缺失的数字。事实上,你可以推广它来找到k个缺失的数。

Qk:如果袋子里少了k个数字,你如何有效地找到它?

这是几个月前的事了,我还不明白这个技巧是什么。显然有一个Ω(N)的时间下限,因为我们必须扫描所有的数字至少一次,但面试官坚持认为,解决技术的时间和空间复杂度(减去O(N)次输入扫描)定义为k而不是N。

所以问题很简单:

如何解决Q2? 你会如何解决Q3? 如何求解Qk?


澄清

Generally there are N numbers from 1..N, not just 1..100. I'm not looking for the obvious set-based solution, e.g. using a bit set, encoding the presence/absence each number by the value of a designated bit, therefore using O(N) bits in additional space. We can't afford any additional space proportional to N. I'm also not looking for the obvious sort-first approach. This and the set-based approach are worth mentioning in an interview (they are easy to implement, and depending on N, can be very practical). I'm looking for the Holy Grail solution (which may or may not be practical to implement, but has the desired asymptotic characteristics nevertheless).

当然,你必须以O(N)为单位扫描输入,但你只能捕获少量的信息(用k而不是N定义),然后必须以某种方式找到k个缺失的数字。


当前回答

我认为可以这样概括:

表示S, M为等差级数和乘法的初始值。

S = 1 + 2 + 3 + 4 + ... n=(n+1)*n/2
M = 1 * 2 * 3 * 4 * .... * n 

我应该考虑一个公式来计算这个,但这不是重点。无论如何,如果缺少一个数字,您已经提供了解决方案。但是,如果少了两个数字,让我们用S1和M1表示新的和和和总倍数,如下所示:

S1 = S - (a + b)....................(1)

Where a and b are the missing numbers.

M1 = M - (a * b)....................(2)

因为你知道S1 M1 M和S,上面的方程是可以解出a和b,缺失的数字。

现在来看看遗漏的三个数字:

S2 = S - ( a + b + c)....................(1)

Where a and b are the missing numbers.

M2 = M - (a * b * c)....................(2)

现在未知量是3而你只有两个方程可以解。

其他回答

我们可以用O(log n)来做Q1和Q2。

假设我们的存储芯片由n个试管阵列组成。试管中的数字x用x毫升化学液体表示。

假设我们的处理器是一束激光。当我们点燃激光时,它垂直穿过所有的管子。每次它通过化学液体,光度就降低1。在某毫升处通过光是O(1)的运算。

现在如果我们在试管的中间点上激光就会得到光度的输出

等于预先计算的值(当没有数字缺失时计算),则缺失的数字大于n/2。 如果我们的输出更小,那么至少有一个小于n/2的数字缺失。我们也可以检查光度是否降低了1或2。如果它减少1,那么一个缺失数小于n/2,另一个大于n/2。如果它减2,那么两个数都小于n/2。

我们可以一次又一次地重复上述过程,缩小我们的问题域。在每一步中,我们将定义域缩小一半。最后我们可以得到结果。

值得一提的是并行算法(因为它们很有趣),

sorting by some parallel algorithm, for example, parallel merge can be done in O(log^3 n) time. And then the missing number can be found by binary search in O(log n) time. Theoretically, if we have n processors then each process can check one of the inputs and set some flag that identifies the number(conveniently in an array). And in the next step each process can check each flag and finally output the number that is not flagged. The whole process will take O(1) time. It has additional O(n) space/memory requirement.

请注意,如上所述,上面提供的两个并行算法可能需要额外的空间。

不确定,这是否是最有效的解决方案,但我会遍历所有条目,并使用bitset来记住,设置了哪些数字,然后测试0位。

我喜欢简单的解决方案,我甚至相信,它可能比计算和,或平方和等更快。

正如@j_random_hacker所指出的,这与在O(n)个时间和O(1)个空间中寻找重复项非常相似,我的答案在这里也适用。

假设“袋子”由一个大小为N - k的基于1的数组a[]表示,我们可以在O(N)个时间和O(k)个额外空间内求解Qk。

首先,我们将数组A[]扩展k个元素,使它现在的大小为n,这是O(k)个额外空间。然后我们运行以下伪代码算法:

for i := n - k + 1 to n
    A[i] := A[1]
end for

for i := 1 to n - k
    while A[A[i]] != A[i] 
        swap(A[i], A[A[i]])
    end while
end for

for i := 1 to n
    if A[i] != i then 
        print i
    end if
end for

第一个循环初始化k个额外的条目,使其与数组中的第一个条目相同(这只是我们知道数组中已经存在的一个方便的值——在这一步之后,大小为N-k的初始数组中缺失的任何条目在扩展数组中仍然缺失)。

第二个循环排列扩展数组,如果元素x至少出现一次,那么其中一个元素将位于位置A[x]。

注意,尽管它有一个嵌套循环,但它仍然在O(N)时间内运行——只有当有一个i使a [i] != i时才会发生交换,并且每次交换设置至少一个元素使a [i] == i,而以前不是这样的。这意味着交换的总数(因此while循环体的执行总数)最多为N-1。

第三个循环打印数组i中没有被值i占用的索引——这意味着i一定是缺失的。

如果一个数字只出现一次,用下面的方法很容易分辨:

创建一个大小为给定数字的布尔数组boolArray;这里是100。

遍历输入数字,并根据数字值将一个元素设置为true。例如,如果找到45,则设置boolArray[45-1] = true;

这是一个O(N)运算。

然后循环遍历boolArray。如果一个元素保持为false,那么element + 1的下标就是缺失的数字。例如,如果boolArray[44]为false,我们就知道第45号丢失了。

这是O(n)运算。空间复杂度为O(1)。

所以这个解可以从一个给定的连续数集中找到任何缺失的数。

您可能需要澄清O(k)的含义。

这里有一个任意k的简单解:对于你的数字集中的每一个v,将2^v相加。最后,循环i从1到n,如果和2^i按位和为零,则i缺失。(或者在数字上,如果和的底除以2^i是偶数。或者模2^(i+1) < 2^i

容易,对吧?O(N)时间,O(1)存储,支持任意k。

除了你在计算一个巨大的数字,在真正的计算机上,每个数字都需要O(N)个空间。事实上,这个解和位向量是一样的。

所以你可以很聪明地计算和,平方和和和立方体的和…直到v^k的和,然后用复杂的数学方法提取结果。但这些都是很大的数字,这就引出了一个问题:我们谈论的是哪种抽象的运作模式?O(1)空间中有多少是合适的,以及需要多长时间才能将所需大小的数字相加?