不久前,我有一次有趣的求职面试经历。这个问题开始很容易:
Q1 : 我们有一个包含数字1, 2, 3, …,的包100。每个数字只出现一次,所以有 100 个数字。现在从袋子里随机抽出一个号码。找到丢失的号码。
1
2
3
100
当然,我以前听过这个面试问题,所以我很快就回答了:
A1:嗯,数字的总和1 + 2 + 3 + … + N是(N+1)(N/2)(参见维基百科:算术级数的总和)。对于N = 100,总和是5050。 因此,如果所有数字都出现在包中,则总和将是5050。由于缺少一个数字,因此总和将小于此数字,差值就是那个数字。O(N)所以我们可以在时间和O(1)空间上找到那个缺失的数字。
A1:嗯,数字的总和1 + 2 + 3 + … + N是(N+1)(N/2)(参见维基百科:算术级数的总和)。对于N = 100,总和是5050。
1 + 2 + 3 + … + N
(N+1)(N/2)
N = 100
5050
因此,如果所有数字都出现在包中,则总和将是5050。由于缺少一个数字,因此总和将小于此数字,差值就是那个数字。O(N)所以我们可以在时间和O(1)空间上找到那个缺失的数字。
O(N)
O(1)
此时我以为我做得很好,但突然之间问题发生了意想不到的转变:
Q2 : 是的,但是现在如果缺少两个数字,你会怎么做?
我以前从未见过/听说过/考虑过这种变化,所以我惊慌失措,无法回答这个问题。面试官坚持要知道我的思维过程,所以我提到也许我们可以通过与预期的产品进行比较来获得更多信息,或者也许在从第一遍收集一些信息之后再做第二遍等等,但我真的只是在拍摄在黑暗中,而不是实际上有一个明确的解决方案。
面试官确实试图鼓励我说有第二个方程确实是解决问题的一种方法。在这一点上,我有点不高兴(因为事先不知道答案),并问这是一种通用的(阅读:“有用的”)编程技术,还是只是一个技巧/陷阱的答案。
面试官的回答让我很吃惊:你可以概括该技术来找到 3 个缺失的数字。实际上,您可以将其推广到找到k个缺失的数字。
Qk:如果袋子里正好缺少k个数字,你将如何有效地找到它?
这是几个月前的事了,我仍然无法弄清楚这种技术是什么。显然有一个Ω(N)时间下限,因为我们必须至少扫描一次所有数字,但面试官坚持认为求解技术的时间和空间复杂度(减去O(N)时间输入扫描)是在k而不是N中定义的。
Ω(N)
所以这里的问题很简单:
同样,您当然必须扫描 中的输入O(N),但您只能捕获少量信息(根据k而不是N定义),然后必须以某种方式找到k缺失的数字。
这是Dimitris Andreou链接的摘要。
记住第 i 次幂的总和,其中 i=1,2,..,k。这减少了求解方程组的问题
a 1 + a 2 + … + a k = b 1
a 1 2 + a 2 2 + … + a k 2 = b 2
…
a 1 k + a 2 k + … + a k k = b k
使用牛顿恒等式,知道 b i允许计算
c 1 = a 1 + a 2 + … a k
c 2 = a 1 a 2 + a 1 a 3 + … + a k-1 a k
c k = a 1 a 2 … a k
如果您展开多项式 (xa 1 )…(xa k ),系数将恰好是 c 1 , …, c k - 参见Vicatte’s formulas。由于每个多项式因子都是唯一的(多项式环是欧几里得域),这意味着 a i是唯一确定的,直到排列。
这结束了一个证明,即记住能力足以恢复数字。对于常数 k,这是一个很好的方法。
然而,当 k 变化时,直接计算 c 1 ,…,c k的方法非常昂贵,因为例如 c k是所有缺失数字的乘积,大小为 n!/(nk)!。为了克服这个问题,在 Z q field中执行计算,其中 q 是一个素数,使得 n <= q < 2n - 它由Bertrand 的假设存在。证明不需要更改,因为公式仍然成立,多项式的因式分解仍然是唯一的。您还需要一种对有限域进行分解的算法,例如Berlekamp或Cantor- Zassenhaus的算法。
常数 k 的高级伪代码:
对于变化的 k,使用例如 Miller-Rabin 找到素数 n <= q < 2n,并执行所有数字以 q 为模减少的步骤。
编辑:此答案的先前版本指出,可以使用特征 2 (q=2^(log n)) 的有限域代替 Z q,其中 q 是素数。情况并非如此,因为牛顿公式需要除以不超过 k 的数字。