在线性时间和恒定空间中查找数组中缺失和重复的元素

Posted

技术标签:

【中文标题】在线性时间和恒定空间中查找数组中缺失和重复的元素【英文标题】:Find the missing and duplicate elements in an array in linear time and constant space 【发布时间】:2011-04-23 21:03:22 【问题描述】:

你得到一个 N 个 64 位整数的数组。 N可能非常大。你知道每个整数 1..N 在数组中出现一次,除了一个整数缺失和一个整数重复。

编写一个线性时间算法来查找丢失和重复的数字。此外,您的算法应该在较小的常量空间中运行,并且保持数组不变。

来源:http://maxschireson.com/2011/04/23/want-a-job-working-on-mongodb-your-first-online-interview-is-in-this-post/

【问题讨论】:

我想你会发现这很有帮助:***.com/questions/3492302/… @JIm:实际上这是不同的。这里我们得到 sum a^k - sum b^k = s 形式的方程,而 sum a^k + sum b^k = s,允许我们使用牛顿恒等式。我们是否也可以在这里使用这些牛顿恒等式似乎并不明显。事实上,这似乎更相关:***.com/questions/5249985/… @Moron:这就是为什么我说我认为他会觉得这很有帮助,而不是投票以重复的方式结束问题。 @Jim:我是说它可能没有人们想象的那么有用。不是在谈论欺骗或任何事情。无论如何... 【参考方案1】:

如果数组中存在所有数字,则总和将为N(N+1)/2

通过在 O(n) 中对数组中的所有数字求和来确定实际总和,设为 Sum(Actual)

缺少一个号码,设为j,一个号码重复,设为k。这意味着

总和(实际)= N(N+1)/2 + k - j

由此而来

k = Sum(实际) -N(N+1)/2 + j

我们还可以计算数组中的平方和,总和为 如果所有数字都存在,则 n3/3 + n2/2 + n/6。

现在我们可以计算 O(n) 中的实际平方和,设为Sum(Actual Squares)

总和(实际平方)=n3/3 + n2/2 + n/6 + k2 - j2

现在我们有两个方程可以确定jk

【讨论】:

不错的分析。我将它推广到 m 带有'N可能非常大',这需要bigInt支持【参考方案2】:

XOR 技巧在两遍中对只读数组起作用。

这样就避免了和和平方和解可能出现的整数溢出问题。

让两个数字分别为xy,其中一个是缺失的数字,另一个是重复的。

XOR 数组的所有元素,以及 1,2,...,N

结果是w = x XOR y

现在由于xy 是不同的,w 不为零。

选择w 的任何非零位。 xy 在这一点上有所不同。假设位的位置是k

现在考虑根据位置 k 的位是 0 还是 1,将数组(和数字 1,2,...,N)分成两组。

现在,如果我们(分别)计算这两组元素的 XOR,结果必须是 xy

由于分割的标准只是检查一个位是否设置,我们可以通过另一个通过数组并有两个变量来计算两组的两个 XOR,每个变量都保存元素的 XOR到目前为止(和1,2,...N),对于每组。最后,当我们完成时,这两个变量将保存xy

相关:

Finding missing elements in an array 可以概括为 m 出现两次,m 缺失。

Find three numbers appeared only once 大约缺少三个。

【讨论】:

是否有理论/书籍可以详细说明这一点?【参考方案3】:

使用related interview question 的基本思想,您可以这样做:

总结所有数字(我们称之为S1)及其平方(S2) 计算预期的数字总和,无需修改,即E1 = n*(n+1)/2E2 = n*(n+1)*(2n+1)/6 现在您知道E1 - S1 = d - mE2 - S2 = d^2 - m^2,其中d 是重复的数字,m 是缺失的数字。

求解这个方程组,你会发现:

m = 1/2 ((E2 - S2)/(E1 - S1) - (E1 - S1))
d = 1/2 ((E2 - S2)/(E1 - S1) + (E1 - S1)) // or even simpler: d = m + (E1 - S1)

.

$S1 = $S2 = 0;
foreach ($nums as $num) 
    $S1 += $num;
    $S2 += $num * $num;


$D1 = $n * ($n + 1) / 2                - $S1;
$D2 = $n * ($n + 1) * (2 * $n + 1) / 6 - $S2;

$m = 1/2 * ($D2/$D1 - $D1);
$d = 1/2 * ($D2/$D1 + $D1);

【讨论】:

如果数字是1..n 而不是1..100,则不是真正的O(n) @IVlad:为什么不是?循环通过n 值是O(n) 不是吗?其他计算是O(1) @nikic - 因为n 64位数字相加的结果不一定是64位数字,所以需要编写代码来处理这么大的整数。该代码不会在恒定时间内运行。平方n 也不是O(1) - 请注意n 没有上限。并不是说您的解决方案是错误的或其他什么 - 我认为在 OP 的限制下不可能做到,我只是说它不是线性的。 @IVlad:我看没问题。平方和需要 191 位的常量,普通和需要 128 位。 @IVlad 将 64 位数字相加或相乘(平方)的行为是常数时间。这种运算的结果最多是一个 128 位的数字,而将所有可能的 128 位数字相加的结果最多是一个 256 位的数字。一个 256 位数字有一个恒定的上界,由于对这些数字进行运算所需的时间与数字的长度有关,因此有一个已知的上界,因此它是一个常数运算。【参考方案4】:

这是一个基于@Aryabhatta 想法的Java 实现: 输入:[3 1 2 5 3] 输出:[3, 4]

public ArrayList<Integer> repeatedNumber(final List<Integer> A) 
    ArrayList<Integer> ret = new ArrayList<>();
    int xor = 0, x = 0, y = 0;
    for(int i=0; i<A.size(); i++) 
        xor ^= A.get(i);
    
    for(int i=1; i<=A.size(); i++) 
        xor ^= i;
    

    int setBit = xor & ~(xor-1);
    for(int i=0; i<A.size(); i++) 
        if((A.get(i) & setBit) != 0) 
            x ^= A.get(i);
         else 
            y ^= A.get(i);
        
    
    for(int i=1; i<=A.size(); i++) 
        if((i & setBit) != 0) 
            x ^= i;
         else 
            y ^= i;
        
    

    for(int i=0; i<A.size(); i++) 
        if(A.get(i) == x) 
            ret.add(x);
            ret.add(y);
            return ret;
         

        if(A.get(i) == y) 
            ret.add(y);
            ret.add(x);
            return ret;
        
    

    return ret;

【讨论】:

【参考方案5】:

BrokenGlass 提出的解决方案涵盖了两个未知数(对应一个重复数和一个缺失数)的情况,使用了两个公式:

公式分别产生 -1 和 -2 阶 n 的 generalized harmonic number。 (幂级数)

通过包含 -3 的 n 阶广义谐波数的值,该解可推广到 3 个未知数。

要解决 m 个未知数(重复数和缺失数),请使用 -1 到 -m 阶 n 的 m 个广义调和数。


Moron 指出,这种方法之前在 *** 中已在 Easy interview question got harder 中讨论过。

【讨论】:

谐波数的形式为 1+ 1/2 + 1/3 + ... + 1/n。 @Moron 我指的是负阶的广义谐波数。 (见en.wikipedia.org/wiki/…) 那些似乎只为 m >= 1 定义,但你说的有道理。这是另一个参考:mathworld.wolfram.com/PowerSum.html。见等式 11。 @Moron 广义谐波数允许 mTable[HarmonicNumber[10, -m], m, 1, 7]。或Sum[p^k, p, 1, n]。感谢您提供 PowerSum 的链接,这似乎也很合适。 @David:我同意你的看法。它对所有 m 都有很好的定义。我误读了 wiki 页面。【参考方案6】:

从字面上理解leave the array untouched 的要求(即数组可以临时修改,只要它最终不改变),可以建议一种面向编程的解决方案。

我假设数组大小 N2^64 小得多,这是一个完全不切实际的内存量。所以我们可以放心地假设N &lt; 2^P 等于P &lt;&lt; 64(明显更小)。换句话说,这意味着数组中的所有数字都有一些未使用的高位。因此,让我们只使用最高位作为标志,该位置的索引是否已在数组中看到。算法如下:

 set HIGH = 2^63  // a number with only the highest bit set
 scan the array, for each number k do
   if array[k] < HIGH: array[k] = array[k] + HIGH // set the highest bit
   else: k is the duplicate
 for each i in 1..N do
   if array[i] < HIGH: i is missing
   else: array[i] = array[i] - HIGH // restore the original number

这是线性时间,计算量很少

【讨论】:

【参考方案7】:
    long long int len = A.size();
    long long int sumOfN = (len * (len+1) ) /2, sumOfNsq = (len * (len +1) *(2*len +1) )/6;
    long long int missingNumber1=0, missingNumber2=0;

    for(int i=0;i<A.size(); i++)
       sumOfN -= (long long int)A[i];
       sumOfNsq -= (long long int)A[i]*(long long int)A[i];
    

    missingno = (sumOfN + sumOfNsq/sumOfN)/2;
    reaptingNO = missingNumber1 - sumOfN;

【讨论】:

【参考方案8】:

假设集合已排序的伪代码

missing = nil
duplicate = nil

for i = 0, i < set.size - 1, i += 1
  if set[i] == set[i + 1]
    duplicate = set[i]
  else if((set[i] + 1) != set[i+1])
    missing = set[i] + 1
  if missing != nil && duplicate != nil
    break

return (missing, duplicate)

【讨论】:

几乎可以肯定,您不能假设数组已排序。

以上是关于在线性时间和恒定空间中查找数组中缺失和重复的元素的主要内容,如果未能解决你的问题,请参考以下文章

在 O(n) 和恒定空间中查找重复

查找数组中重复的唯一元素+时间复杂度O(n)+空间复杂度O

在线性时间内创建图案化阵列 [重复]

在 O(1) 空间中的数组中查找重复元素(数字不在任何范围内)

堆排序(heap sort)

在未排序的数组 1 到 100 中查找 2 个缺失的数字。(Java)[重复]