前一段时间我有一次有趣的面试经历。 这个问题开始很容易:
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。
所以这里的问题很简单:
O(N)
位。 我们无法承受与N成正比的任何额外空间。 同样,当然,您必须扫描O(N)
的输入,但是您只能捕获少量信息(以k而不是N定义),然后必须以某种方式找到k个缺失的数字。
我认为无需任何复杂的数学方程式和理论即可完成此操作。 以下是就地和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 GetMissingNumbers(int[] arrayOfNumbers)
{
List missingNumbers = new List();
int arrayLength = arrayOfNumbers.Length;
//First Pass
for (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 numbers
for (int i = 0; i < arrayLength; i++)
{
//If this index is unvisited, means this is a missing number
if (arrayOfNumbers[i] > 0)
{
missingNumbers.Add(i + 1);
}
}
return missingNumbers;
}
我请一个4岁的孩子解决这个问题。 他对数字进行了排序,然后进行了计数。 它的空间要求为O(厨房地板),并且工作原理很简单,但是缺少许多球。
可能的解决方案:
public class MissingNumber {
public static void main(String[] args) {
// 0-20
int [] 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);
}
}
}
我认为可以这样概括:
将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,而您只有两个方程式可以解决。
这是一个使用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 present
for (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 ones
for (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; }
}
}
我不知道这是否有效,但我想提出这个解决方案。
现在运行一个循环以获取可能的对(p,q),它们都位于[1,100]中,并求和为d。
当获得一对时,检查(结果3)XOR p = q,如果是,则完成。
如果我错了,请纠正我,如果正确,还请评论时间复杂度
如果您拥有两个列表的总和以及两个列表的乘积,则可以求解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函数的复杂性,所以我无法解决该解决方案的复杂性(如果有人知道,请在下面评论)。
您可以根据对称性(以数学语言表示的组)思考解决方案,从而激发解决方案。 无论数字集的顺序如何,答案都应该相同。 如果要使用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
。
要解决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个分区将包含多个丢失的数字)。 但是,我不确定添加更多丢失的数字后性能会下降多少。
这是一个不使用就地分区的实现,因此此示例不满足空间要求,但确实说明了算法的步骤:
演示版
我们可以通过将数字本身和数字的平方相加来求解Q2。
然后我们可以将问题减少到
k1 + k2 = x
k1^2 + k2^2 = y
其中x
和y
是总和低于期望值的距离。
替换给我们:
(x-k2)^2 + k2^2 = y
然后我们可以解决以确定丢失的数字。
你能检查每个数字是否存在吗? 如果是,您可以尝试以下方法:
S =袋子中所有数字的总和(S <5050)
Z =缺失数的总和5050-S
如果缺少的数字是x
和y
则:
x = Z-y和
max(x)= Z-1
所以您检查从1
到max(x)
并找到数字
不知道这是否是最有效的解决方案,但我会遍历所有条目,并使用位集记住设置了哪些数字,然后测试0位。
我喜欢简单的解决方案-甚至我相信,这可能比计算总和或平方和等速度要快。
我还没有检查数学,但我怀疑在计算Σ(n^2)
的同一遍中计算Σ(n^2)
Σ(n)
是否可以提供足够的信息来获取两个缺失的数字,如果也要进行Σ(n^3)
一共有三个,依此类推。
非常好的问题。 我会为Qk使用设置差异。 许多编程语言甚至都支持它,例如Ruby:
missing = (1..100).to_a - bag
这可能不是最有效的解决方案,但是如果我在这种情况下面临这样的任务(已知边界,低边界),我将在现实生活中使用它。 如果数量的集合非常大,那么我当然会考虑一种更有效的算法,但是在那之前,简单的解决方案对我来说就足够了。
等一下。 如问题所述,袋子里有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的列表中。
您可以通过阅读Muthukrishnan-数据流算法:难题1:找到缺失的数字的几页找到它。 它精确地显示了您要寻找的概括 。 可能这是您的面试官阅读的内容以及他提出这些问题的原因。
现在,如果只有人们开始删除Muthukrishnan的待遇所包含或取代的答案,并使此文本更容易找到。 :)
另请参见sdcvvc的直接相关答案 ,该答案还包括伪代码(欢呼!无需阅读那些棘手的数学公式:))(谢谢, 干得好!)。
这是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的高级伪代码:
对于变化的k,使用例如Miller-Rabin求素数n <= q <2n,并执行所有以q为模的数字简化的步骤。
编辑:此答案的先前版本指出,代替Z q ,其中q是质数,可以使用特征2(q = 2 ^(log n))的有限域。 事实并非如此,因为牛顿公式需要除以不超过k的数字。
这听起来可能很愚蠢,但是在向您提出的第一个问题中,您将必须查看袋子中所有剩余的数字,然后将它们实际加起来以使用该方程式找到缺失的数字。
因此,由于您可以看到所有数字,因此只需查找丢失的数字即可。 当缺少两个数字时也是如此。 我认为很简单。 当您看到袋子中剩余的数字时,使用方程式毫无意义。
基于数字和的解决方案的问题是它们没有考虑存储和处理具有大指数的数字的成本...在实践中,因为它适用于非常大的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 =
let
mid = (low + high) `div` 2
klow = countInRange low mid
khigh = krange - klow
in
findMissing (findMissing low mid klow) (mid + 1) high khigh
in
findMising 1 (n - k) k
使用的存储: O(k)
用于列表, O(log(n))
用于堆栈: O(k + log(n))
此算法更直观,具有相同的时间复杂度并且使用较少的空间。
您可以尝试使用布隆过滤器 。 将购物袋中的每个数字插入花色中,然后遍历完整的1-k集,直到报告未找到的每个数字为止。 这可能无法在所有情况下都找到答案,但是可能是一个很好的解决方案。
对于该问题,我将采用不同的方法,并向面试官调查有关他正在尝试解决的更大问题的更多详细信息。 根据问题及其周围的需求,显而易见的基于集合的解决方案可能是正确的选择,而生成列表并通过后续选择的方法则可能不正确。
例如,可能是访问员要分发n
消息,并且需要知道未导致回复的k
,并且在第nk
次回复到来之后,需要在尽可能短的挂钟时间内知道它。 我们也可以说,消息通道的本质是,即使全速运行,也有足够的时间在消息之间进行一些处理,而不会影响最后一个答复到达后产生最终结果所花费的时间。 可以花时间将每个已发送消息的某些识别方面插入集合中,并在每个相应答复到达时将其删除。 一旦最后一个答复到达,唯一要做的就是从集合中删除其标识符,在典型的实现中,这需要O(log k+1)
。 之后,该集合包含k
缺失元素的列表,并且无需执行其他处理。
这肯定不是批处理预先生成的数字袋的最快方法,因为整个过程运行O((log 1 + log 2 + ... + log n) + (log n + log n-1 + ... + log k))
。 但是它确实适用于k
任何值(即使事先不知道),在上面的示例中,它以最小化最关键间隔的方式应用。
我相信我有一个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)
时间复杂度
有一种通用的方法可以概括这样的流算法。 想法是使用一些随机化方法,希望将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
。
实际上,通过使用注释中描述的技巧,我们甚至可以比幂和方法更有效率。
对于Q2,这是一个效率不高的解决方案,但仍具有O(N)运行时并占用O(k)空间。
这个想法是运行原始算法两次。 在第一个中,您会得到一个缺少的总数,它为您提供了缺失数的上限。 我们称这个数字N
您知道丢失的两个数字的总和为N
,因此第一个数字只能在[1, floor((N-1)/2)]
区间内[1, floor((N-1)/2)]
而第二个数字将在[floor(N/2)+1,N-1]
。
因此,您将再次循环所有数字,并丢弃第一个间隔中未包括的所有数字。 是的,您跟踪它们的总和。 最后,您将知道遗漏的两个数字之一,并进一步扩展第二个数字。
我感觉这种方法可以推广,也许在一次输入遍历过程中多个搜索可以“并行”运行,但是我还没有弄清楚该怎么做。
您可能需要澄清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)空间适合多大,需要多少时间才能得出所需大小的数字?
大多数时候,我们可以在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)
空间/内存要求。 注意, 上面提供的两个并行算法可能需要额外的空间,如注释中所述 。
另一方法是使用残差图滤波。
假设我们有数字1至4,而缺少3。 二进制表示如下:
1 = 001b,2 = 010b,3 = 011b,4 = 100b
我可以创建如下流程图。
1
1 -------------> 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
。
假设ArrayList对象(myList)充满了这些数字,其中缺少两个数字x和y,因此可能的解决方案可以是:
int k = 1;
while (k < 100) {
if (!myList.contains(k)) {
System.out.println("Missing No:" + k);
}
k++;
}
正如@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 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
额外的条目初始化为与数组中的第一个条目相同(这只是一个方便的值,我们知道数组中已经存在该值-在此步骤之后,初始大小数组中缺少的所有条目扩展数组中仍然缺少Nk
)。
第二个循环置换扩展数组,以便如果元素x
至少存在一次,则这些条目之一将在位置A[x]
。
请注意,尽管它具有嵌套循环,但仍会在O(N)
时间中运行-仅当存在i
这样A[i] != i
,才会发生交换,并且每个交换都设置至少一个元素,使得A[i] == i
,以前不是这样。 这意味着交换的总数(以及while
循环主体的执行总数)最多为N-1
。
第三个循环将打印数组i
中未被值i
占用的索引-这意味着i
一定已经丢失了。
可能此算法可以解决问题1:
甚至更好:
def GetValue(A)
val=0
for i=1 to 100
do
val=val^i
done
for value in A:
do
val=val^value
done
return val
实际上,可以将该算法扩展为两个缺失的数字。 第一步保持不变。 当我们用两个缺失的数字调用GetValue时,结果将是a1^a2
是两个缺失的数字。 可以说
val = a1^a2
现在要从val中筛选出a1和a2,我们可以获取val中的任何设置位。 假设第ith
位设置为val。 这意味着a1和a2在ith
位位置具有不同的奇偶校验。 现在我们对原始数组进行另一个迭代,并保留两个xor值。 一个代表第i位,第2个未i位。 现在,我们有两个存储桶,其保证a1 and a2
位于不同的存储桶中。 现在,重复我们在每个存储桶中找到一个缺失元素的操作。