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

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个缺失的数字。


当前回答

您可能需要澄清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)空间中有多少是合适的,以及需要多长时间才能将所需大小的数字相加?

其他回答

你可以通过阅读Muthukrishnan的几页-数据流算法:谜题1:寻找缺失的数字来找到它。它准确地显示了您正在寻找的泛化。也许这就是面试官读到的内容,也是他提出这些问题的原因。


还请参阅sdcvvc的直接相关答案,其中还包括伪代码(万岁!没有必要阅读那些棘手的数学公式:)(谢谢,干得好!)

您可能需要澄清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)空间中有多少是合适的,以及需要多长时间才能将所需大小的数字相加?

一种方法是对质数101取模。

计算并存储整数1到100的乘积,对该数字取101的模。小外显:结果是1。

计算并存储所有数字1到100的和,对结果对101进行模运算。小exo:结果是0。

现在假设袋子里的数字x和y都被拿走了。

求包里所有东西对101模的乘积和。这样我就知道的值

A = x+y和 b = x * y

modulo 101。

现在很容易求出x和y对101的模(求解含有101个元素的有限域上的二次多项式)。

现在你知道了x和y对101的模。但是因为你知道x和y都小于101,所以你知道它们的真实值。

您可以使用二分搜索来查找缺失(或连续)数字的间隔。运行时间应该是(num间隔)* log(平均间隔长度)* n。如果间隔不多,则很有用。

非常好的问题。我会用Qk的集合差。很多编程语言甚至都支持它,比如Ruby:

missing = (1..100).to_a - bag

这可能不是最有效的解决方案,但如果我在这种情况下面临这样的任务(已知边界,低边界),这是我在现实生活中会使用的解决方案。如果数字集非常大,那么我当然会考虑一个更有效的算法,但在此之前,简单的解决方案对我来说已经足够了。