找到两个缺失的数字
Posted
技术标签:
【中文标题】找到两个缺失的数字【英文标题】:Find two missing numbers 【发布时间】:2012-04-18 22:08:07 【问题描述】:我们有一台内存为 O(1) 的机器,我们想在第一遍中(一个接一个)传递 n
数字,然后我们排除这两个数字,我们会将 n-2
数字传递给机器.
编写一个查找缺失数字的算法。
【问题讨论】:
抱歉,我也记不住数字了。 数字应该是一个系列吗? [1,....,n] 你需要找到缺失的元素吗? 这在 O(1) 内存中是不可能的。 @Shedal 大概意思是“恒定的内存量”。 @jpalecek 这不是重复的。 OP 说(在评论中)这些数字不在一个范围内。 【参考方案1】:可以用 O(1) 内存完成。
您只需要几个整数来跟踪一些运行总和。整数不需要 log n 位(其中 n 是输入整数的数量),它们只需要 2b+1 位,其中 b 是单个输入整数中的位数。
当您第一次读取流时,添加所有数字及其所有平方,即对于每个输入数字 n,请执行以下操作:
sum += n
sq_sum += n*n
然后在第二个流上对两个不同的值 sum2 和 sq_sum2 执行相同的操作。现在做以下数学运算:
sum - sum2 = a + b
sq_sum - sq_sum2 = a^2 + b^2
(a + b)(a + b) = a^2 + b^2 + 2ab
(a + b)(a + b) - (a^2 + b^2) = 2ab
(sum*sum - sq_sum) = 2ab
(a - b)(a - b) = a^2 + b^2 - 2ab
= sq_sum - (sum*sum - sq_sum) = 2sq_sum - sum*sum
sqrt(2sq_sum - sum*sum) = sqrt((a - b)(a - b)) = a - b
((a + b) - (a - b)) / 2 = b
(a + b) - b = a
所有中间结果都需要 2b+1 位,因为您要存储两个输入整数的乘积,并且在一种情况下将这些值之一乘以 2。
【讨论】:
假设数字是不同的正整数[这是一个有效的输入],那么最大的数字至少是n
,这需要logn
位来表示——因此答案仍然是@987654325 @ 空间,因为 b
本身在 logn
中是线性的。另外:OP 确实在 cmets 中提到了 数字不一定来自某个范围。我同意这是O(1)
,如果我们可以假设整数最多为 32 位 [或任何其他常数...]
附注这个假设 [constant number of bits] 与 O(1)
无能力相矛盾,因为“输入不受限制”的说法不正确,如果 int 具有恒定数量的位,则可能的答案数量有限。没有这个假设,O(1)
不可行就成立了。
说这个问题不能在常量空间中解决就是说我们不能一次在内存中保存来自输入的单个整数,在这种情况下问题只是愚蠢的。由于我们显然必须有这么多内存,称之为 M,它在 O(1) 约束内需要 kM 内存,对于这个解决方案,k 约为 10。
我猜 sq_sum2 的函数是足够的,因为我猜 x^2+Y^2=c 的方程在整数域中有一个唯一的答案......但我无法证明这个问题
@amink,x^2 + y^2 = c 在整数中具有唯一答案是不正确的。例如,如果 c 为 4225,则 (x,y) 可以是 (16,63)、(25, 60)、(33,56)、(39,52) 中的任何一个,不包括它们的排列。【参考方案2】:
假设数字介于 1..N 之间,其中 2 个缺失 - x
和 y
,您可以执行以下操作:
使用高斯公式:sum = N(N+1)/2
sum - actual_sum = x + y
使用数字的乘积:product = 1*2..*N = N!
product - actual_product = x * y
解决 x,y 并且您有丢失的数字。
简而言之-遍历数组并将每个元素相加得到actual_sum
,将每个元素相乘得到actual_product
。然后求解x
和y
的两个方程。
【讨论】:
N 个数字的总和不能存储在 O(1) 内存中。 我同意,但我认为这是面试官的意思要问的——我认为你不能做得更好。 他还指出 [in cmets] 数字不是范围 [1,...,n]。我认为这是一个向面试官展示你了解架构限制的技巧问题。 @Shedal:n个正整数之和至少为n
,需要logn
位表示。
@Shedal 将其应用于运行时的方式相同。如果对于某些常量c
和N
,算法在处理所有n
大小的n > N
的输入时最多消耗c*f(n)
字节的内存,则该算法的内存消耗在O(f(n))
中。跨度>
【参考方案3】:
O(1)
内存无法做到这一点。
假设您有一个常量 k
位内存 - 那么您的算法可以有 2^k
可能的状态。
但是 - 输入不受限制,假设 (2^k) + 1
不同的问题案例有 (2^k) + 1
可能的答案,从 piegeonhole principle,对于具有不同答案的 2 个问题,您将返回相同的答案两次,因此您的算法错了。
【讨论】:
但是如果数字在整数范围内可能是常数,可以在 O(1) 中假设 32 位整数的数量 [我是通过他的 c++ 标签说的,似乎不是 CS 问题]跨度> @SaeedAmiri:如果存在这样的假设 - 我认为它应该是明确的,而不是隐含在这些类型的问题中。 我同意了,所以我问了,即使这样假设我也想不出任何算法:) 无论如何你的证明是不正确的,例如回溯算法通常是 P-SPACE,例如 n 空间,但它们可能的状态可以是 2^2^2^..... @SaeedAmiri 为什么矛盾? P-Space 允许线性、二次或任何多项式空间量,让它为p(n)
,因此您可以拥有2^p(n)
数量的状态。你能举一个这个解决方案失败的具体反例吗?或者你能给出一个使用多项式内存的具有 2^2^2^...n 个状态的算法吗?【参考方案4】:
当我读完这个问题时,我想到了以下内容。但是上面的答案表明,使用 O(1) 内存是不可能的,或者应该对数字范围进行限制。如果我对问题的理解有误,请告诉我。好的,就到这里
您拥有 O(1) 内存 - 这意味着您拥有 恒定数量的内存。
当 n 个数字第一次传递给您时,只需继续将它们添加到一个变量中并继续将它们相乘到另一个变量中。因此,在第一遍结束时,您将得到 2 个变量 S1 和 P1 中所有数字的总和和乘积。到目前为止,您已经使用了 2 个变量(如果您读取内存中的数字,则 +1)。
当 (n-2) 数字第二次传递给您时,请执行相同操作。将 (n-2) 个数的总和和乘积存储在另外 2 个变量 S2 和 P2 中。到目前为止,您已经使用了 4 个变量(如果您读取内存中的数字,则 +1)。
如果缺少的两个数字是x和y,那么
x + y = S1 - S2
x*y = P1/P2;
你在两个变量中有两个方程。解决它们。
所以你使用了一个恒定数量的内存(与 n 无关)。
【讨论】:
这类似于@BrokenGlass 建议的方法 - 阅读那里的 cmets,看看为什么它不是O(1)
内存。请注意,这在 n
中甚至不是线性的 - 因为所有元素的乘积是 O(n!
) [假设唯一元素],您需要 O(log(n!)) = O(nlogn)
来表示它。【参考方案5】:
void Missing(int arr[], int size)
int xor = arr[0]; /* Will hold xor of all elements */
int set_bit_no; /* Will have only single set bit of xor */
int i;
int n = size - 2;
int x = 0, y = 0;
/* Get the xor of all elements in arr[] and 1, 2 .. n */
for(i = 1; i < size; i++)
xor ^= arr[i];
for(i = 1; i <= n; i++)
xor ^= i;
/* Get the rightmost set bit in set_bit_no */
set_bit_no = xor & ~(xor-1);
/* Now divide elements in two sets by comparing rightmost set
bit of xor with bit at same position in each element. */
for(i = 0; i < size; i++)
if(arr[i] & set_bit_no)
x = x ^ arr[i]; /*XOR of first set in arr[] */
else
y = y ^ arr[i]; /*XOR of second set in arr[] */
for(i = 1; i <= n; i++)
if(i & set_bit_no)
x = x ^ i; /*XOR of first set in arr[] and 1, 2, ...n */
else
y = y ^ i; /*XOR of second set in arr[] and 1, 2, ...n */
printf("\n The two repeating missing elements are are %d & %d ", x, y);
【讨论】:
【参考方案6】:请查看下面的解决方案链接。它解释了 XOR 方法。 这种方法比上面解释的任何方法都更有效。 它可能与上面的 Victor 相同,但有一个解释为什么会这样。
Solution here
【讨论】:
【参考方案7】:这是不需要任何二次公式或乘法的简单解决方案:
假设 B 是两个缺失数字的总和。
这组缺少的两个数字将来自: (1,B-1),(2,B-1)...(B-1,1)
因此,我们知道这两个数字之一将小于或等于 B 的一半。
我们知道我们可以计算 B(两个缺失数字的总和)。
所以,一旦我们有了 B,我们将找到列表中所有小于或等于 B/2 的数字的总和,然后从 (1 到 B/2) 的总和中减去它,得到第一个数字.然后,我们通过从 B 中减去第一个数字得到第二个数字。在下面的代码中,rem_sum 是 B。
public int[] findMissingTwoNumbers(int [] list, int N)
if(list.length == 0 || list.length != N - 2)return new int[0];
int rem_sum = (N*(N + 1))/2;
for(int i = 0; i < list.length; i++)rem_sum -= list[i];
int half = rem_sum/2;
if(rem_sum%2 == 0)half--; //both numbers cannot be the same
int rem_half = getRemHalf(list,half);
int [] result = rem_half, rem_sum - rem_half;
return result;
private int getRemHalf(int [] list, int half)
int rem_half = (half*(half + 1))/2;
for(int i = 0; i < list.length; i++)
if(list[i] <= half)rem_half -= list[i];
return rem_half;
【讨论】:
以上是关于找到两个缺失的数字的主要内容,如果未能解决你的问题,请参考以下文章
如何在 Oracle 中找到两个大小范围之间缺失的大小范围?