容易的面试问题变得更加困难:给定数字1..100,在正好缺少k的情况下,找到缺失的数字
前一段时间我有一次有趣的面试经历。 这个问题开始很容易:
Q1:我们有一个包含数字的包
1
,2
,3
,...,100
。 每个数字仅出现一次,因此有100个数字。 现在,从袋子中随机抽取一个号码。 查找丢失的号码。
我当然已经听过这个面试问题,所以我很快就回答了以下问题:
A1 :恩,数字
1 + 2 + 3 + … + N
的总和为(N+1)(N/2)
(请参阅Wikipedia:算术级数之和 )。 对于N = 100
,总和为5050
。因此,如果袋子中所有数字都存在,则总和为
5050
。 由于缺少一个数字,所以总和小于这个数字,而差就是那个数字。 因此我们可以找到O(N)
时间和O(1)
空间中的缺失数。
在这一点上,我认为我做得不错,但是突然之间,这个问题突然发生了变化:
Q2 :是的,但是现在如果缺少两个数字,您将如何处理?
我之前从未见过/听过/考虑过这种变化,所以我感到惊慌,无法回答这个问题。 面试官坚持要知道我的思维过程,所以我提到也许我们可以通过与预期产品进行比较来获得更多信息,或者也许在从第一遍收集到一些信息之后再进行第二遍,等等,但是我真的只是在拍摄在黑暗中,而不是真正找到解决方案的清晰途径。
面试官的确通过说第二个方程式确实是解决问题的一种方式来鼓励我。 在这一点上,我有点不高兴(因为事先不知道答案),并询问这是否是一种通用的(读作:“有用的”)编程技术,还是仅仅是一个技巧/陷阱。
面试官的回答让我感到惊讶:您可以推广该技术以找到3个缺失的数字。 实际上,您可以对其进行概括以找到k个缺失数字。
Qk :如果袋子中恰好缺少k个数字,您将如何有效地找到它?
这是几个月前,但我仍然不知道这种技术是什么。 显然,存在一个Ω(N)
时间下限,因为我们必须至少扫描一次所有数字,但是访调员坚持认为,求解技术的TIME和SPACE复杂度(减去O(N)
时间输入扫描)定义为k不是N。
所以这里的问题很简单:
- 您将如何解决Q2 ?
- 您将如何解决第3季度 ?
- 您将如何解决Qk ?
澄清说明
- 通常,从1 .. N开始有N个数字,而不仅仅是1..100。
- 我不是在寻找明显的基于集合的解决方案,例如使用位集 ,通过指定位的值编码每个数字的存在/不存在,因此在其他空间中使用
O(N)
位。 我们无法承受与N成正比的任何额外空间。 - 我也不在寻找明显的排序优先方法。 这种方法和基于集合的方法在采访中值得一提(它们易于实现,并且取决于N ,可能非常实用)。 我正在寻找“圣杯”解决方案(可能实现或可能不实际,但仍具有所需的渐近特性)。
同样,当然,您必须扫描O(N)
的输入,但是您只能捕获少量信息(以k而不是N定义),然后必须以某种方式找到k个缺失的数字。
#1楼
我认为无需任何复杂的数学方程式和理论即可完成此操作。 以下是就地和O(2n)时间复杂度解决方案的建议:
输入表单假设:
袋中的数字数= n
缺失数字的数量= k
袋子中的数字由长度为n的数组表示
算法的输入数组的长度= n
数组中缺少的条目(从包装袋中取出的数字)将替换为数组中第一个元素的值。
例如。 最初的包看起来像[2,9,3,7,8,6,4,5,1,10]。 如果取出4,则4的值将变为2(数组的第一个元素)。 因此,在取出4个袋子之后,袋子看起来像[2,9,3,7,8,6,2,5,1,10]
该解决方案的关键是在遍历数组时,通过否定该索引的值来标记访问的数字的索引。
IEnumerable<int> GetMissingNumbers(int[] arrayOfNumbers){List<int> missingNumbers = new List<int>();int arrayLength = arrayOfNumbers.Length;//First Passfor (int i = 0; i < arrayLength; i++){int index = Math.Abs(arrayOfNumbers[i]) - 1;if (index > -1){arrayOfNumbers[index] = Math.Abs(arrayOfNumbers[index]) * -1; //Marking the visited indexes}}//Second Pass to get missing numbersfor (int i = 0; i < arrayLength; i++){ //If this index is unvisited, means this is a missing numberif (arrayOfNumbers[i] > 0){missingNumbers.Add(i + 1);}}return missingNumbers;}
#2楼
我请一个4岁的孩子解决这个问题。 他对数字进行了排序,然后进行了计数。 它的空间要求为O(厨房地板),并且工作原理很简单,但是缺少许多球。
#3楼
可能的解决方案:
public class MissingNumber {public static void main(String[] args) {// 0-20int [] a = {1,4,3,6,7,9,8,11,10,12,15,18,14};printMissingNumbers(a,20);}public static void printMissingNumbers(int [] a, int upperLimit){int b [] = new int[upperLimit];for(int i = 0; i < a.length; i++){b[a[i]] = 1;}for(int k = 0; k < upperLimit; k++){if(b[k] == 0)System.out.println(k);}}
}
#4楼
我认为可以这样概括:
将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,而您只有两个方程式可以解决。
#5楼
这是一个使用k位额外存储的解决方案,没有任何巧妙的技巧,也很简单。 执行时间O(n),额外空间O(k)。 只是为了证明可以解决这一问题,而无需先阅读解决方案或成为天才:
void puzzle (int* data, int n, bool* extra, int k)
{// data contains n distinct numbers from 1 to n + k, extra provides// space for k extra bits. // Rearrange the array so there are (even) even numbers at the start// and (odd) odd numbers at the end.int even = 0, odd = 0;while (even + odd < n){if (data [even] % 2 == 0) ++even;else if (data [n - 1 - odd] % 2 == 1) ++odd;else { int tmp = data [even]; data [even] = data [n - 1 - odd]; data [n - 1 - odd] = tmp; ++even; ++odd; }}// Erase the lowest bits of all numbers and set the extra bits to 0.for (int i = even; i < n; ++i) data [i] -= 1;for (int i = 0; i < k; ++i) extra [i] = false;// Set a bit for every number that is presentfor (int i = 0; i < n; ++i){int tmp = data [i];tmp -= (tmp % 2);if (i >= even) ++tmp;if (tmp <= n) data [tmp - 1] += 1; else extra [tmp - n - 1] = true;}// Print out the missing onesfor (int i = 1; i <= n; ++i)if (data [i - 1] % 2 == 0) printf ("Number %d is missing\n", i);for (int i = n + 1; i <= n + k; ++i)if (! extra [i - n - 1]) printf ("Number %d is missing\n", i);// Restore the lowest bits again.for (int i = 0; i < n; ++i) {if (i < even) { if (data [i] % 2 != 0) data [i] -= 1; }else { if (data [i] % 2 == 0) data [i] += 1; }}
}
#6楼
我不知道这是否有效,但我想提出这个解决方案。
- 计算100个元素的异或
- 计算98个元素的异或(删除2个元素之后)
- 现在(结果为1)XOR(结果为2)为您提供两个缺失编号i..ea的异或,如果a和b是缺失元素
4,使用通常的求和公式diff的方法来获取缺失的No的总和,并说diff为d
现在运行一个循环以获取可能的对(p,q),它们都位于[1,100]中,并求和为d。
当获得一对时,检查(结果3)XOR p = q,如果是,则完成。
如果我错了,请纠正我,如果正确,还请评论时间复杂度
#7楼
如果您拥有两个列表的总和以及两个列表的乘积,则可以求解Q2。
(l1是原始列表,l2是修改后的列表)
d = sum(l1) - sum(l2)
m = mul(l1) / mul(l2)
我们可以对此进行优化,因为算术级数的总和是第一项和最后一项的平均值的n倍:
n = len(l1)
d = (n/2)*(n+1) - sum(l2)
现在我们知道(如果a和b是删除的数字):
a + b = d
a * b = m
因此,我们可以重新安排为:
a = s - b
b * (s - b) = m
并乘以:
-b^2 + s*b = m
并重新排列,使右侧为零:
-b^2 + s*b - m = 0
然后我们可以用二次方程式求解:
b = (-s + sqrt(s^2 - (4*-1*-m)))/-2
a = s - b
示例Python 3代码:
from functools import reduce
import operator
import math
x = list(range(1,21))
sx = (len(x)/2)*(len(x)+1)
x.remove(15)
x.remove(5)
mul = lambda l: reduce(operator.mul,l)
s = sx - sum(x)
m = mul(range(1,21)) / mul(x)
b = (-s + math.sqrt(s**2 - (-4*(-m))))/-2
a = s - b
print(a,b) #15,5
我不知道sqrt,reduce和sum函数的复杂性,所以我无法解决该解决方案的复杂性(如果有人知道,请在下面评论)。
#8楼
您可以根据对称性(以数学语言表示的组)思考解决方案,从而激发解决方案。 无论数字集的顺序如何,答案都应该相同。 如果要使用k
函数来帮助确定缺少的元素,则应考虑哪些函数具有该属性:对称。 函数s_1(x) = x_1 + x_2 + ... + x_n
是对称函数的一个示例,但还有其他更高次的函数。 特别要考虑基本对称函数 。 s_2(x) = x_1 x_2 + x_1 x_3 + ... + x_1 x_n + x_2 x_3 + ... + x_(n-1) x_n
2的基本对称函数为s_2(x) = x_1 x_2 + x_1 x_3 + ... + x_1 x_n + x_2 x_3 + ... + x_(n-1) x_n
,即两个元素的所有乘积之和。 对于阶数为3或更高的基本对称函数也是如此。 它们显然是对称的。 此外,事实证明它们是所有对称功能的基础。
您可以通过注意s_2(x,x_(n+1)) = s_2(x) + s_1(x)(x_(n+1))
来构建基本对称函数。 进一步的考虑应该使您确信s_3(x,x_(n+1)) = s_3(x) + s_2(x)(x_(n+1))
等,因此可以一次计算。
我们如何知道数组中缺少哪些项目? 考虑多项式(z-x_1)(z-x_2)...(z-x_n)
。 如果输入任何数字x_i
则其值为0
。 展开多项式,得到z^n-s_1(x)z^(n-1)+ ... + (-1)^n s_n
。 基本对称函数也出现在这里,这实在不足为奇,因为如果我们对根应用任何置换,多项式应该保持不变。
因此,我们可以构建多项式并尝试对其进行分解,以找出哪些数字不在集合中,如其他人所提到的那样。
最后,如果我们担心大量内存溢出(第n个对称多项式的阶为100!
),则可以执行这些计算mod p
,其中p
是质数大于100的情况。在这种情况下,我们评估多项式mod p
并发现,当输入是集合中的数字时,它再次求值为0
当输入是集合中非数字时,它求值为非零值。 但是,正如其他人指出的那样,为了及时从依赖于k
而不是N
的多项式中获取值,我们必须考虑多项式mod p
。
#9楼
要解决2(和3)个缺失数字的问题,您可以修改quickselect
,它平均在O(n)
运行,如果就地进行分区,则使用常量内存。
相对于随机枢轴
p
将集合划分为分区l
和r
,分区l
包含小于枢轴的数字,r
包含大于枢轴的数字。通过将枢轴值与每个分区的大小进行比较,确定2个缺失数字所在的分区(
p - 1 - count(l) = count of missing numbers in l
和n - count(r) - p = count of missing numbers in r
p - 1 - count(l) = count of missing numbers in l
n - count(r) - p = count of missing numbers in r
)a)如果每个分区缺少一个数字,则使用总和差法查找每个丢失的数字。
(1 + 2 + ... + (p-1)) - sum(l) = missing #1
和((p+1) + (p+2) ... + n) - sum(r) = missing #2
b)如果一个分区缺少两个数字并且该分区为空,则丢失的数字为
(p-1,p-2)
或(p+1,p+2)
具体取决于哪个分区缺少数字。如果一个分区缺少2个数字但不为空,则递归到该分区。
由于只有2个缺失数字,因此该算法始终丢弃至少一个分区,因此保留了快速选择的O(n)
平均时间复杂度。 同样,对于3个丢失的数字,该算法每次通过还会丢弃至少一个分区(因为2个丢失的数字,最多只有1个分区将包含多个丢失的数字)。 但是,我不确定添加更多丢失的数字后性能会下降多少。
这是一个不使用就地分区的实现,因此此示例不满足空间要求,但确实说明了算法的步骤:
<?php$list = range(1,100);unset($list[3]);unset($list[31]);findMissing($list,1,100);function findMissing($list, $min, $max) {if(empty($list)) {print_r(range($min, $max));return;}$l = $r = [];$pivot = array_pop($list);foreach($list as $number) {if($number < $pivot) {$l[] = $number;}else {$r[] = $number;}}if(count($l) == $pivot - $min - 1) {// only 1 missing number use difference of sumsprint array_sum(range($min, $pivot-1)) - array_sum($l) . "\n";}else if(count($l) < $pivot - $min) {// more than 1 missing number, recursefindMissing($l, $min, $pivot-1);}if(count($r) == $max - $pivot - 1) {// only 1 missing number use difference of sumsprint array_sum(range($pivot + 1, $max)) - array_sum($r) . "\n";} else if(count($r) < $max - $pivot) {// mroe than 1 missing number recursefindMissing($r, $pivot+1, $max);}}
演示版
#10楼
我们可以通过将数字本身和数字的平方相加来求解Q2。
然后我们可以将问题减少到
k1 + k2 = x
k1^2 + k2^2 = y
其中x
和y
是总和低于期望值的距离。
替换给我们:
(x-k2)^2 + k2^2 = y
然后我们可以解决以确定丢失的数字。
#11楼
你能检查每个数字是否存在吗? 如果是,您可以尝试以下方法:
S =袋子中所有数字的总和(S <5050)
Z =缺失数的总和5050-S
如果缺少的数字是x
和y
则:
x = Z-y和
max(x)= Z-1
所以您检查从1
到max(x)
并找到数字
#12楼
不知道这是否是最有效的解决方案,但我会遍历所有条目,并使用位集记住设置了哪些数字,然后测试0位。
我喜欢简单的解决方案-甚至我相信,这可能比计算总和或平方和等速度要快。
#13楼
我还没有检查数学,但我怀疑在计算Σ(n^2)
的同一遍中计算Σ(n^2)
Σ(n)
是否可以提供足够的信息来获取两个缺失的数字,如果也要进行Σ(n^3)
一共有三个,依此类推。
#14楼
非常好的问题。 我会为Qk使用设置差异。 许多编程语言甚至都支持它,例如Ruby:
missing = (1..100).to_a - bag
这可能不是最有效的解决方案,但是如果我在这种情况下面临这样的任务(已知边界,低边界),我将在现实生活中使用它。 如果数量的集合非常大,那么我当然会考虑一种更有效的算法,但是在那之前,简单的解决方案对我来说就足够了。
#15楼
等一下。 如问题所述,袋子里有100个数字。 无论k有多大,都可以在固定时间内解决该问题,因为您可以使用一个集合并最多在一个循环中进行100-k次迭代从集合中删除数字。 100是常数。 剩余数字集就是您的答案。
如果我们将解决方案推广到从1到N的数字,则除N不是常数外,没有任何变化,因此我们处于O(N-k)= O(N)时间。 例如,如果使用位集,则在O(N)时间中将位设置为1,迭代数字,然后将位设置为0(O(Nk)= O(N)),然后有答案。
在我看来,面试官在问您如何在O(k)时间而不是O(N)时间中打印最终集的内容。 显然,在设置了某个位后,您必须遍历所有N个位以确定是否应打印该数字。 但是,如果您更改实现集合的方式,则可以在k次迭代中打印出数字。 这是通过将数字放入要存储在哈希集和双向链表中的对象中来完成的。 从哈希集中删除对象时,也将从列表中将其删除。 答案将留在长度为k的列表中。
#16楼
您可以通过阅读Muthukrishnan-数据流算法:难题1:找到缺失的数字的几页找到它。 它精确地显示了您要寻找的概括 。 可能这是您的面试官阅读的内容以及他提出这些问题的原因。
现在,如果只有人们开始删除Muthukrishnan的待遇所包含或取代的答案,并使此文本更容易找到。 :)
另请参见sdcvvc的直接相关答案 ,该答案还包括伪代码(欢呼!无需阅读那些棘手的数学公式:))(谢谢, 干得好!)。
#17楼
这是Dimitris Andreou的 链接的摘要。
记住第i次幂的总和,其中i = 1,2,..,k。 这减少了求解方程组的问题
a 1 + a 2 + ... + a k = b 1
a 12 + a 22 + ... + a k2 = b 2
...
a 1k + a 2k + ... + a kk = 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公式 。 由于每个多项式因子都是唯一的(多项式的环是一个欧几里德域 ),因此这意味着a i是唯一确定的,直到置换。
这样就证明了记忆力足以恢复数字。 对于常数k,这是一个好方法。
然而,当k变化时,直接计算c 1 ,...,c k的方法是非常昂贵的,因为例如,c k是所有缺失数n 1 /(nk)1的乘积。 为了克服这个问题,请在Z q字段中执行计算 ,其中q是质数,使得n <= q <2n-根据Bertrand的假设存在。 由于公式仍然成立,并且多项式的因式分解仍然是唯一的,因此无需更改证明。 您还需要一种用于对有限域进行因子分解的算法,例如Berlekamp或Cantor-Zassenhaus提出的算法 。
常数k的高级伪代码:
- 计算给定数字的第i次幂
- 减去可得到未知数的第i次幂。 求和b i 。
- 使用牛顿的恒等式从b i计算系数; 称他们为c i 。 基本上,c 1 = b 1 ; c 2 =(c 1 b 1 -b 2 )/ 2; 有关详细公式,请参阅Wikipedia
- 分解多项式x k -c 1 x k-1 + ... + c k 。
- 多项式的根是所需的数字a 1 ,...,a k 。
对于变化的k,使用例如Miller-Rabin求素数n <= q <2n,并执行所有以q为模的数字简化的步骤。
编辑:此答案的先前版本指出,代替Z q ,其中q是质数,可以使用特征2(q = 2 ^(log n))的有限域。 事实并非如此,因为牛顿公式需要除以不超过k的数字。
#18楼
这听起来可能很愚蠢,但是在向您提出的第一个问题中,您将必须查看袋子中所有剩余的数字,然后将它们实际加起来以使用该方程式找到缺失的数字。
因此,由于您可以看到所有数字,因此只需查找丢失的数字即可。 当缺少两个数字时也是如此。 我认为很简单。 当您看到袋子中剩余的数字时,使用方程式毫无意义。
#19楼
基于数字和的解决方案的问题是它们没有考虑存储和处理具有大指数的数字的成本...在实践中,因为它适用于非常大的n,所以将使用大数字库。 我们可以分析这些算法的空间利用率。
我们可以分析sdcvvc和Dimitris Andreou算法的时间和空间复杂性。
存储:
l_j = ceil (log_2 (sum_{i=1}^n i^j))
l_j > log_2 n^j (assuming n >= 0, k >= 0)
l_j > j log_2 n \in \Omega(j log n)l_j < log_2 ((sum_{i=1}^n i)^j) + 1
l_j < j log_2 (n) + j log_2 (n + 1) - j log_2 (2) + 1
l_j < j log_2 n + j + c \in O(j log n)`
所以l_j \\in \\Theta(j log n)
使用的总存储空间: \\sum_{j=1}^k l_j \\in \\Theta(k^2 log n)
使用的空间:假设计算a^j
需要ceil(log_2 j)
时间,总时间:
t = k ceil(\sum_i=1^n log_2 (i)) = k ceil(log_2 (\prod_i=1^n (i)))
t > k log_2 (n^n + O(n^(n-1)))
t > k log_2 (n^n) = kn log_2 (n) \in \Omega(kn log n)
t < k log_2 (\prod_i=1^n i^i) + 1
t < kn log_2 (n) + 1 \in O(kn log n)
总使用时间: \\Theta(kn log n)
如果这个时间和空间令人满意,则可以使用简单的递归算法。 令b!i为袋中的第i个条目,n为移除前的数字,k为移除数。 用Haskell语法...
let-- O(1)isInRange low high v = (v >= low) && (v <= high)-- O(n - k)countInRange low high = sum $ map (fromEnum . isInRange low high . (!)b) [1..(n-k)]findMissing l low high krange-- O(1) if there is nothing to find.| krange=0 = l-- O(1) if there is only one possibility.| low=high = low:l-- Otherwise total of O(knlog(n)) time| otherwise =letmid = (low + high) `div` 2klow = countInRange low midkhigh = krange - klowinfindMissing (findMissing low mid klow) (mid + 1) high khigh
infindMising 1 (n - k) k
使用的存储: O(k)
用于列表, O(log(n))
用于堆栈: O(k + log(n))
此算法更直观,具有相同的时间复杂度并且使用较少的空间。
#20楼
您可以尝试使用布隆过滤器 。 将购物袋中的每个数字插入花色中,然后遍历完整的1-k集,直到报告未找到的每个数字为止。 这可能无法在所有情况下都找到答案,但是可能是一个很好的解决方案。
#21楼
对于该问题,我将采用不同的方法,并向面试官调查有关他正在尝试解决的更大问题的更多详细信息。 根据问题及其周围的需求,显而易见的基于集合的解决方案可能是正确的选择,而生成列表并通过后续选择的方法则可能不正确。
例如,可能是访问员要分发n
消息,并且需要知道未导致回复的k
,并且在第nk
次回复到来之后,需要在尽可能短的挂钟时间内知道它。 我们也可以说,消息通道的本质是,即使全速运行,也有足够的时间在消息之间进行一些处理,而不会影响最后一个答复到达后产生最终结果所花费的时间。 可以花时间将每个已发送消息的某些识别方面插入集合中,并在每个相应答复到达时将其删除。 一旦最后一个答复到达,唯一要做的就是从集合中删除其标识符,在典型的实现中,这需要O(log k+1)
。 之后,该集合包含k
缺失元素的列表,并且无需执行其他处理。
这肯定不是批处理预先生成的数字袋的最快方法,因为整个过程运行O((log 1 + log 2 + ... + log n) + (log n + log n-1 + ... + log k))
。 但是它确实适用于k
任何值(即使事先不知道),在上面的示例中,它以最小化最关键间隔的方式应用。
#22楼
我相信我有一个O(k)
时间和O(log(k))
空间算法,因为您具有可用于任意大整数的floor(x)
和log2(x)
函数:
您有一个k
位长整数(因此为log8(k)
空间),在其中添加x^2
,其中x是在包中找到的下一个数字: s=1^2+2^2+...
这需要O(N)
时间(对于访问者而言这不是问题)。 最后,您会得到j=floor(log2(s))
,这是您要查找的最大数字。 然后s=sj
,然后再次执行上述操作:
for (i = 0 ; i < k ; i++)
{j = floor(log2(s));missing[i] = j;s -= j;
}
现在,您通常没有2756
位整数的floor和log2函数,而是使用了double。 所以? 简而言之,对于每2个字节(或1、3或4),您可以使用这些函数来获取所需的数字,但这会增加O(N)
时间复杂度
#23楼
有一种通用的方法可以概括这样的流算法。 想法是使用一些随机化方法,希望将k
元素“散布”为独立的子问题,我们的原始算法可以在其中为我们解决问题。 除其他事项外,该技术还用于稀疏信号重建。
- 制作一个大小为
u = k^2
的数组a
。 - 选择任何通用哈希函数
h : {1,...,n} -> {1,...,u}
。 (像乘移 ) - 对于
1, ..., n
每个i
1, ..., n
增加a[h(i)] += i
- 对于输入流中的每个数字
x
,减a[h(x)] -= x
。
如果所有缺失的数字都已散列到不同的存储桶中,则数组的非零元素现在将包含缺失的数字。
根据通用哈希函数的定义,特定对发送到同一存储桶的概率小于1/u
。 由于大约有k^2/2
对,因此错误概率最多为k^2/2/u=1/2
。 也就是说,我们成功的可能性至少为50%,如果我们增加u
我们的机会就会增加。
注意,该算法占用k^2 logn
位的空间(我们需要每个数组存储区的logn
位。)这与@Dimitris Andreou的答案所需的空间匹配(特别是多项式因式分解的空间要求,该空间恰好也是随机的。)该算法的每次更新时间也恒定,而不是功率求和时的时间k
。
实际上,通过使用注释中描述的技巧,我们甚至可以比幂和方法更有效率。
#24楼
对于Q2,这是一个效率不高的解决方案,但仍具有O(N)运行时并占用O(k)空间。
这个想法是运行原始算法两次。 在第一个中,您会得到一个缺少的总数,它为您提供了缺失数的上限。 我们称这个数字N
您知道丢失的两个数字的总和为N
,因此第一个数字只能在[1, floor((N-1)/2)]
区间内[1, floor((N-1)/2)]
而第二个数字将在[floor(N/2)+1,N-1]
。
因此,您将再次循环所有数字,并丢弃第一个间隔中未包括的所有数字。 是的,您跟踪它们的总和。 最后,您将知道遗漏的两个数字之一,并进一步扩展第二个数字。
我感觉这种方法可以推广,也许在一次输入遍历过程中多个搜索可以“并行”运行,但是我还没有弄清楚该怎么做。
#25楼
您可能需要澄清O(k)的含义。
这是任意k的平凡解决方案:对于一组数字中的每个v,累加2 ^ v的总和。 最后,将i从1循环到N。如果与2 ^ i按位与的和为零,则i丢失。 (或者从数值上讲,如果总和的下限除以2 ^ i是偶数。或者sum modulo 2^(i+1)) < 2^i
。
容易吧? O(N)时间,O(1)存储,它支持任意k。
除了您要计算大量的数据以外,实际计算机上的每个数字都需要O(N)空间。 实际上,该解决方案与位向量相同。
因此,您可能很聪明,可以计算平方和,平方和与立方和……直到v ^ k的总和,然后进行漂亮的数学运算以提取结果。 但是,这些数字也很大,这引出了一个问题:我们在谈论什么抽象的运营模式? O(1)空间适合多大,需要多少时间才能得出所需大小的数字?
#26楼
大多数时候,我们可以在O(log n)中执行Q1和Q2 。
假设我们的memory chip
由n
个test tubes
的阵列组成。 试管中的数字x
用化学液体的x
milliliter
表示。
假设我们的处理器是laser light
。 当我们点亮激光时,它垂直于激光管的长度横穿所有电子管。 每次通过化学液体时,光度降低1
。 并且以某毫升标记通过光是O(1)
的操作。
现在,如果我们在试管中间点燃激光并获得光度输出
- 等于一个预先计算的值(当没有数字缺失时计算),则缺失的数字大于
n/2
。 - 如果我们的输出较小,则至少有一个小于
n/2
缺失数。 我们还可以检查亮度是否降低了1
或2
。 如果将其减少1
则一个缺失数小于n/2
而另一个缺失数大于n/2
。 如果将其减少2
则两个数字均小于n/2
。
我们可以一次又一次地重复上述过程,缩小我们的问题范围。 在每一步中,我们都将域缩小一半。 最后我们可以得到我们的结果。
值得一提的并行算法(因为它们很有趣),
- 通过某种并行算法进行排序,例如,可以在
O(log^3 n)
时间内完成并行合并。 然后可以通过O(log n)
时间的二进制搜索找到丢失的数字。 - 从理论上讲,如果我们有
n
处理器,则每个进程都可以检查输入之一并设置一些标识该数字的标志(方便地放置在数组中)。 并且在下一步中,每个进程都可以检查每个标志,并最终输出未标记的数字。 整个过程将花费O(1)
时间。 它具有额外的O(n)
空间/内存要求。
注意, 上面提供的两个并行算法可能需要额外的空间,如注释中所述 。
#27楼
另一方法是使用残差图滤波。
假设我们有数字1至4,而缺少3。 二进制表示如下:
1 = 001b,2 = 010b,3 = 011b,4 = 100b
我可以创建如下流程图。
11 -------------> 1| | 2 | 1 |
0 ---------> 1 ----------> 0 |
| | |
| 1 1 | |
0 ---------> 0 ----------> 0 || |1 | 1 |
1 ---------> 0 -------------> 1
请注意,流程图包含x个节点,而x是位数。 边的最大数量为(2 * x)-2。
因此,对于32位整数,它将占用O(32)空间或O(1)空间。
现在,如果我删除了从1,2,4开始的每个数字的容量,则剩下一个残差图。
0 ----------> 1 ---------> 1
最后,我将运行如下所示的循环,
result = []for x in range(1,n):exists_path_in_residual_graph(x)result.append(x)
现在结果是result
包含不遗漏的数字(假阳性)。 但是,当缺少k
元素时, k <=(结果的大小)<= n 。
我将最后一次浏览给定的列表以标记结果是否丢失。
因此,时间复杂度将为O(n)。
最后,可以通过采取节点以减少假阳性(和所需要的空间)的数00
, 01
, 11
, 10
,而不是仅仅0
和1
。
#28楼
假设ArrayList对象(myList)充满了这些数字,其中缺少两个数字x和y,因此可能的解决方案可以是:
int k = 1;while (k < 100) {if (!myList.contains(k)) {System.out.println("Missing No:" + k);}k++;}
#29楼
正如@j_random_hacker指出的那样,这与在O(n)时间和O(1)空间中查找重复项非常相似, 在那里对我的答案进行修改也可以在这里工作。
假设“ bag”由大小为N - k
的基于1的数组A[]
N - k
,我们可以在O(N)
时间和O(k)
额外空间中求解Qk。
首先,我们用k
元素扩展数组A[]
,使其现在的大小为N
这是O(k)
额外空间。 然后,我们运行以下伪代码算法:
for i := n - k + 1 to nA[i] := A[1]
end forfor i := 1 to n - kwhile A[A[i]] != A[i] swap(A[i], A[A[i]])end while
end forfor i := 1 to nif A[i] != i then print iend if
end for
第一个循环将k
额外的条目初始化为与数组中的第一个条目相同(这只是一个方便的值,我们知道数组中已经存在该值-在此步骤之后,初始大小数组中缺少的所有条目扩展数组中仍然缺少Nk
)。
第二个循环置换扩展数组,以便如果元素x
至少存在一次,则这些条目之一将在位置A[x]
。
请注意,尽管它具有嵌套循环,但仍会在O(N)
时间中运行-仅当存在i
这样A[i] != i
,才会发生交换,并且每个交换都设置至少一个元素,使得A[i] == i
,以前不是这样。 这意味着交换的总数(以及while
循环主体的执行总数)最多为N-1
。
第三个循环将打印数组i
中未被值i
占用的索引-这意味着i
一定已经丢失了。
#30楼
可能此算法可以解决问题1:
- 预计算前100个整数的异或(val = 1 ^ 2 ^ 3 ^ 4 .... 100)
- 对元素保持来自输入流的异或(val1 = val1 ^ next_input)
- 最终答案= val ^ val1
甚至更好:
def GetValue(A)val=0for i=1 to 100doval=val^idonefor value in A:doval=val^value donereturn val
实际上,可以将该算法扩展为两个缺失的数字。 第一步保持不变。 当我们用两个缺失的数字调用GetValue时,结果将是a1^a2
是两个缺失的数字。 可以说
val = a1^a2
现在要从val中筛选出a1和a2,我们可以获取val中的任何设置位。 假设第ith
位设置为val。 这意味着a1和a2在ith
位位置具有不同的奇偶校验。 现在我们对原始数组进行另一个迭代,并保留两个xor值。 一个代表第i位,第2个未i位。 现在,我们有两个存储桶,其保证a1 and a2
位于不同的存储桶中。 现在,重复我们在每个存储桶中找到一个缺失元素的操作。
容易的面试问题变得更加困难:给定数字1..100,在正好缺少k的情况下,找到缺失的数字相关推荐
- 简单介绍三个C语言图形库C语言其实最擅长的是写纯数据处理的程序 . 非得用C语言写个界面程序那将会变得很困难 . 我刚开始学C语言就是从hello world 开始的 , 后来慢慢开始学从三个数中找
C语言其实最擅长的是写纯数据处理的程序 . 非得用C语言写个界面程序那将会变得很困难 . 我刚开始学C语言就是从hello world 开始的 , 后来慢慢开始学从三个数中找出最大值 , 和对数组进行 ...
- cad图纸导入ai尺寸变了_AI公司导入欧洲个人数据变得更加困难
cad图纸导入ai尺寸变了 In a judgment issued last week, the European Court of Justice invalidated the EU-U.S. ...
- 12家国内外之名公司多场面试,微软到谷歌.让我们通过学习达到100%面试率与100%通过率...
下面内容来源于_lcu_,我摘要了一些大家值得借鉴与参考的东西,与大家一起学习成长,来源地址:http://www.cnblogs.com/figure9/archive/2013/01/09/285 ...
- 只导表前10条数据_【205期】面试官:数据量很大的情况下,对于分页查询你有什么优化方案吗?...
点击上方"Java面试题精选",关注公众号 面试刷图,查缺补漏 >>号外:公众号改版后文章顺序不固定,欢迎大家把我们面试题精选这个公众号设置为星标,感谢大家一年的支持! ...
- Java面试必刷的200道真题,100%好评!
选择 在现在这个浮躁而又拜金的社会,我相信很多人做技术并非出于热爱,只是被互联网的高薪吸引,毕竟技术岗位非常枯燥,不仅要面对奇奇怪怪的需求,还要不停的充实自己避免被淘汰.所以想要吃好技术这碗饭并不容易 ...
- Vue——计算属性(计算属性简介、计算属性和方法的区别:(面试)、关于计算属性 函数什么情况下调用、案例)
目录 计算属性(面试) 1.计算属性简介 2.计算属性和方法的区别:(面试) 3.关于计算属性 函数什么情况下调用 4.案例:购物车页面计算总价 计算属性(面试) 1.计算属性简介 把c ...
- Interview:算法岗位面试—10.30上午上海某信息公司(偏图算法)技术面试之单链表反转、给定整型数组和目标值 二分法查找+下午上海某金融公司(AI岗位,上市)CTO和主管技术面试之Xcepti
ML岗位面试:10.30上午上海某信息公司(偏图算法)技术面试之单链表反转.给定整型数组和目标值 二分法查找+下午上海某金融公司(AI岗位,上市)CTO和主管技术面试之Xception.推荐算法等 I ...
- ElasticSearch面试 - es 在数据量很大的情况下如何提高查询效率啊?
面试题 es 在数据量很大的情况下(数十亿级别)如何提高查询效率啊? 面试官心理分析 这个问题是肯定要问的,说白了,就是看你有没有实际干过 es,因为啥?其实 es 性能并没有你想象中那么好的.很多时 ...
- 面试官:数据量大的情况下分页查询很慢,有什么优化方案?
来源: http://uee.me/aVSnD 当需要从数据库查询的表有上万条记录的时候,一次性查询所有结果会变得很慢,特别是随着数据量的增加特别明显,这时需要使用分页查询.对于数据库分页查询,也有很 ...
- 面试官问:在读多写少的情况下,如何优化 MySQL 的数据查询方案
作者 | 面试官问 责编 | 张文 来源 | 面试官问(ID:interviewer_asked) 面试官问:假设你负责的某业务在双十一期间要搞运营活动,公司投入了大量的营销费用进行推广,此举 ...
最新文章
- web项目性能优化--网络、js、渲染
- 基于云计算的海量数据挖掘
- Nacos 发布 v0.8.0 Pre-GA版本,安全稳定上生产 1
- 北风设计模式课程---里氏替换原则(Liskov Substitution Principle)
- 调查内存泄漏第2部分–分析问题
- linux网络文件系统包括,Linux文件系统
- 远程网络教育计算机统考试题及答案,2019年电大远程网络教育计算机应用基础统考题库真题试题附全答案...
- 安装arm虚拟机_虚拟机Parallels出手:苹果M1的Mac能运行Win 10 还挺顺畅
- pytorch实战从入门到精通第三部分——数据处理
- 顺序表的动态存储细节
- 查看现有Exchange 2010数据库大小
- Oracle数据库 - 安装教程
- java抽奖代码_纯java代码实现抽奖系统
- 发声计算机在线,文字转语音软件(文字转语音真人发声免费版)
- lcx端口转发linux_技术干货 | 内网渗透之代理转发
- 人脸关键点检测PFLD论文解读
- Python基础《十七:XML解析》
- 4g内存php一般开多少个进程,Linux_4G内存服务器epoll并发量最大能达到多少?,按照题主的意思 是根据内存去 - phpStudy...
- 三菱FX3U/FX1N底层源码,PLSR源码, 总体功能和指令可能支持在RUN中下载程序,支持注释的写入和读取,支持脉冲输出与定位指令(包括PLSY /PWM/PLSR/PLSV/DRVI /DRV
- 【项目总结】基于SpringBoot+Ansj分词+正倒排索引的Java文档搜索引擎项目总结
热门文章
- B2B企业越早做网络营销会有哪些优势 由上海添力张进老师讲解
- easyUI1.7学习第一天_menu表单
- Android Camera模块(二)
- [BZOJ3993]-[SDOI2015]星际战争-二分答案+最大流
- jq小插件tiptip
- 文档型漏洞攻击研究报告
- android 重力感应代码,Android编程实现的重力感应示例代码
- 【FFT】HDU4609-3 idiots
- 轻型异常在线检测器LODA:Lightweight on-line detector of anomalies
- 六年一轮回:大数据改变的,不仅仅是我的专业!