在时间O(n)中查找数组中的重复元素

Mar*_*yeh 63 java arrays algorithm

在求职面试中我被问到这个问题,我一直想知道正确的答案.

您有一个从0到n-1的数字数组,其中一个数字被删除,并替换为数组中已有的数字,该数字与该数字重复.我们如何在时间O(n)中检测到这个重复?

例如,1,2,3,4将成为一个数组1,2,2,4.

时间O(n 2)的简单解决方案是使用嵌套循环来查找每个元素的副本.

ric*_*ici 151

这可以在O(n)时间和O(1)空间上完成.

(该算法仅起作用,因为数字是已知范围内的连续整数):

在单次通过向量时,计算所有数字的总和,以及所有数字的平方和.

从中减去所有数字的总和N(N-1)/2.叫这个A.

从中减去平方和N(N-1)(2N-1)/6.除以此A.调用结果B.

删除(B + A)/2的号码是,它被替换的号码是(B - A)/2.

例:

矢量是[0, 1, 1, 2, 3, 5]:

  • N = 6

  • 矢量和为0 + 1 + 1 + 2 + 3 + 5 = 12.N(N-1)/ 2为15. A = 3.

  • 平方和为0 + 1 + 1 + 4 + 9 + 25 = 40.N(N-1)(2N-1)/ 6为55.B =(55-40)/ A = 5.

  • 删除的数字是(5 + 3)/ 2 = 4.

  • 被替换的数字是(5 - 3)/ 2 = 1.

为什么会这样:

  • 原始矢量的总和[0, ..., N-1]N(N-1)/2.假设该值a已被删除并替换为b.现在修改后的矢量之和将是N(N-1)/2 + b - a.如果我们从中N(N-1)/2得到修改后的矢量的总和a - b.所以A = a - b.

  • 类似地,原始矢量的平方和是N(N-1)(2N-1)/6.修改后的矢量的平方和是.从原始总和中减去修改后的矢量的平方和得到,与之相同.因此,如果我们将它除以(即),我们得到.N(N-1)(2N-1)/6 + b2 - a2a2 - b2(a+b)(a-b)a - bAB = a + b

  • 现在B + A = a + b + a - b = 2aB - A = a + b - (a - b) = 2b.

  • 如果数字是连续的整数,只需检查前后的数字.如果您正在查看的那个出现问题,那么您就得到了答案.如果它按顺序但等于它的前任继承者,那么你就有了副本.为什么需要所有花哨的数学? (4认同)
  • @imray:假设数组未排序。 (2认同)

qPC*_*vir 34

我们有原始数组int A[N];创建第二个数组bool B[N],类型bool=false.迭代第一个数组并设置B[A[i]]=trueif是否为false,否则bing!

  • 我同意数字是否为整数.但是,如果A []中的数字不是整数呢?我认为哈希表在这种一般情况下运行良好. (4认同)
  • @bitfox即使A []是整数,如果A []中的任何整数大于N,算法也不起作用. (4认同)
  • @qchen如原始问题所述:"你有一个从0到N-1的数字数组".在A数组中可以找到的最大值是N.因此,当您使用[A [i]]时,不会产生数组索引错误. (2认同)

小智 31

您可以在O(N)时间内完成,而无需任何额外空间.以下是算法的工作原理:

以下列方式遍历数组:

  1. 对于遇到的每个元素,将其对应的索引值设置为负数.例如:如果找到[0] = 2.得到[2]并否定该值.

    通过执行此操作,您可以将其标记为遇到.既然你知道你不能有负数,你也知道你是否是否定了它.

  2. 检查对应于该值的索引是否已标记为否定,如果是,则获取重复元素.例如:如果a [0] = 2,转到[2]并检查它是否为负数.

让我们说你有以下数组:

int a[]  = {2,1,2,3,4};
Run Code Online (Sandbox Code Playgroud)

在第一个元素后,您的数组将是:

int a[] = {2,1,-2,3,4};
Run Code Online (Sandbox Code Playgroud)

在第二个元素后,您的数组将是:

int a[] = {2,-1,-2,3,4};
Run Code Online (Sandbox Code Playgroud)

当你到达第三个元素时,你会转到[2]并看到它已经是负数.你得到了副本.

  • 如果其中一个元素是10,000而不是4,该怎么办?它将超出数组范围. (5认同)
  • @apadana这个答案仅在`input array是0 - > n - 1时才有效,其中0和n`之间的整数 (5认同)
  • 如果我们在一个数组中得到两个0,我不认为逻辑会起作用.例如,数组是{0,1,2,3,4,5},其中3替换为0. (2认同)

Evg*_*uev 11

扫描阵列3次:

  1. 将所有数组元素XOR组合在一起 - > A.将0到N-1 - >之间的所有数字进行异或B.现在A XOR B = X XOR D,其中X是删除的元素,D是重复的元素.
  2. 选择任何非零位A XOR B.将此位设置的所有数组元素XOR组合在一起 - > A1.将从0到N-1的所有数字进行异或运算,其中该位置位 - > B1.现在要么A1 XOR B1 = XA1 XOR B1 = D.
  3. 再次扫描阵列并尝试查找 A1 XOR B1.如果找到,则这是重复元素.如果不是,则重复元素是A XOR B XOR A1 XOR B1.

  • @prashant O(3n)与O(n)相同 - 系数在大O表示法中被忽略. (5认同)

par*_*fal 7

使用a HashSet来保存已经看过的所有数字.它在(摊销)O(1)时间运作,因此总计是O(N).

  • @AlexWien - 你看到我说"摊销"了吗?重新散列不依赖于数组中元素的数量,因此仍然被认为是"O(1)".另外,您可以预先设置该组的大小,以便您根本不进行重组.当然,如果你有`Long.MAX_VALUE`项,由于桌面上的物理限制,你最终会在添加/检索上出现'O(N)`语义,但大多数人都不会考虑(就像他们不喜欢不要认为Quicksort在最坏的情况下有"O(N ^ 2)"行为. (2认同)

Pat*_*han 7

我建议使用BitSet.我们知道N对于数组索引来说足够小,所以BitSet的大小合理.

对于数组的每个元素,请检查与其值对应的位.如果已经设置,那就是重复.如果没有,请设置该位.