我有一段时间有一个有趣的面试经历。问题开始很简单:
Q1:我们有一个包含数字的包
1
,2
,3
,...,100
。每个数字只出现一次,因此有 100 个数字。现在从包里随机挑出一个号码。找到丢失的号码。
当然,我之前听过这个采访问题,所以我很快回答了以下问题:
A1 :嗯,数字
1 + 2 + 3 + … + N
的总和是(N+1)(N/2)
(参见维基百科:算术系列之和 )。对于N = 100
,总和为5050
。因此,如果包中存在所有数字,则总和将精确为
5050
。由于缺少一个数字,总和将小于此数值,差异就是该数字。因此我们可以在O(N)
时间和O(1)
空间中找到丢失的数字。
在这一点上,我认为我做得很好,但突然之间,这个问题发生了意想不到的变化:
Q2 :这是正确的,但是现在如果缺少两个数字你会怎么做?
我之前从未见过 / 听过 / 考虑过这种变化,所以我惊慌失措,无法回答这个问题。面试官坚持要知道我的思考过程,所以我提到也许我们可以通过与预期产品进行比较来获得更多信息,或者可能在从第一遍获得一些信息后再做第二遍,但我真的只是拍摄在黑暗中而不是实际上有一条清晰的解决方案。
面试官确实试图鼓励我说有第二个等式确实是解决问题的一种方法。在这一点上,我有点不高兴(因为事先不知道答案),并询问这是一般的(阅读:“有用”)编程技术,还是只是一个技巧 / 问题答案。
面试官的回答让我感到惊讶:你可以概括一下找到 3 个缺失数字的技巧。实际上,您可以将其概括为找到k 个缺失的数字。
Qk :如果行李中缺少k 个号码,您会如何有效地找到它?
这是几个月前,我仍然无法弄清楚这种技术是什么。显然有一个Ω(N)
时间下界,因为我们必须扫描所有数字至少一次,但是采访者坚持认为解决技术的时间和空间复杂度(减去O(N)
时间输入扫描)在k 中定义不是N.
所以这里的问题很简单:
O(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 - 请参阅Viète 的公式 。由于每个多项式因子唯一(多项式环是欧几里德域 ),这意味着i是唯一确定的,直到排列。
这结束了一个证据,即记住功率足以恢复数字。对于常数 k,这是一个很好的方法。
然而,当 k 变化时,计算 c 1 ,...,c k的直接方法是非常昂贵的,因为例如 c k是所有缺失数的乘积,幅度为 n!/(nk)!。为了克服这一点, 在 Z q字段中执行计算 ,其中 q 是素数,使得 n <= q <2n - 它由Bertrand 的假设存在。证明不需要改变,因为公式仍然成立,并且多项式的因子分解仍然是唯一的。您还需要一种用于有限域分解的算法,例如Berlekamp或Cantor-Zassenhaus 的算法 。
常数 k 的高级伪代码:
对于变化的 k,使用例如 Miller-Rabin 找到素数 n <= q <2n,并执行所有以 q 为模的数量减少的步骤。
编辑:该答案的先前版本表明,代替 Z q ,其中 q 是素数,可以使用特征 2 的有限域(q = 2 ^(log n))。情况并非如此,因为牛顿的公式需要按数字除以 k。
你可以通过阅读Muthukrishnan的几页来找到它- 数据流算法:拼图 1:找到丢失的数字 。 它显示了您正在寻找的概括 。这可能是你的面试官阅读的内容以及他提出这些问题的原因。
现在,如果只有人们会开始删除被 Muthukrishnan 治疗所包含或取代的答案,并使这个文本更容易找到。 :)
我们可以通过将数字本身和数字的平方相加来求解 Q2。
然后我们可以将问题减少到
k1 + k2 = x
k1^2 + k2^2 = y
其中x
和y
是总和低于预期值的程度。
替代给了我们:
(x-k2)^2 + k2^2 = y
然后我们可以解决以确定我们缺少的数字。