【问题标题】:Finding out the duplicate element in an array找出数组中的重复元素
【发布时间】:2011-10-30 08:24:47
【问题描述】:

有一个大小为 n 的数组,数组中包含的元素介于 1 和 n-1 之间,因此每个元素出现一次,并且只有一个元素出现多次。我们需要找到这个元素。

虽然这是一个非常常见的问题,但我仍然没有找到正确的答案。大多数建议是我应该将数组中的所有元素相加,然后从中减去所有索引的总和,但如果元素的数量非常大,这将不起作用。它会溢出。也有关于使用异或门dup = dup ^ arr[i] ^ i的建议,我不清楚。

我想出了这个算法,它是对加法算法的增强,将在很大程度上减少溢出的机会!

for i=0 to n-1
  begin :
    diff = A[i] - i;
    sum  = sum + diff;
  end

diff 包含重复元素,但使用此方法我无法找出重复元素的索引。为此,我需要再次遍历数组,这是不可取的。任何人都可以提出一个更好的解决方案,不涉及加法或 XOR 方法在 O(n) 中工作?

【问题讨论】:

  • 这只是Finding duplicates in O(n) time and O(1) space中问题的一个更简单的例子
  • "为此,我需要再次遍历数组,这是不可取的" 为什么这不可取?第二次遍历数组不会改变算法的复杂度。
  • @caf:那里的解决方案修改了数组,这在这里似乎不受欢迎。
  • @sepp2k:它不会改变复杂性,但无论如何都会使算法变慢(例如,与一次性算法相比)。
  • “diff 包含重复元素”。具体如何?最后,diff == (A[n-1] - (n-1))sum 有什么用?您没有在任何地方使用最终结果。

标签: c arrays algorithm complexity-theory big-o


【解决方案1】:

您可以通过多种方式来思考这个问题,具体取决于您的问题描述的限制条件。

如果你确实知道一个元素是重复的,那么有很多方法可以解决这个问题。一种特别聪明的解决方案是使用按位异或运算符。 XOR 具有以下有趣的特性:

  1. XOR 是关联的,所以 (x ^ y) ^ z = x ^ (y ^ z)
  2. XOR 是可交换的:x ^ y = y ^ x
  3. XOR 是它自己的逆:x ^ y = 0 iff x = y
  4. XOR 的标识为零:x ^ 0 = x

这里的属性 (1) 和 (2) 意味着当对一组值进行 XOR 时,将 XOR 应用于元素的顺序无关紧要。您可以根据需要重新排序元素或对它们进行分组。属性 (3) 意味着如果你多次异或相同的值,你会得到零,属性 (4) 意味着如果你对任何东西与 0 进行异或,你会得到原来的数字。将所有这些属性放在一起,您会得到一个有趣的结果:如果对一组数字进行 XOR,则结果是该组中出现奇数次的所有数字的 XOR。这样做的原因是,当您对出现偶数次的数字进行异或运算时,您可以将这些数字的异或分解成一组对。每对通过 (3) 异或到 0,并且所有这些零的组合异或通过 (4) 返回零。因此,所有偶重数都被抵消了。

要使用它来解决原始问题,请执行以下操作。首先,将列表中的所有数字异或。这给出了出现奇数次的所有数字的异或,最终是从 1 到 (n-1) 的所有数字,除了重复项。现在,将这个值与从 1 到 (n-1) 的所有数字进行异或运算。然后,这使得 1 到 (n-1) 范围内的所有先前未被取消的数字都被取消,只留下重复的值。此外,这在 O(n) 时间内运行并且仅使用 O(1) 空间,因为所有值的 XOR 都适合单个整数。

在您的原始帖子中,您考虑了一种替代方法,该方法利用从 1 到 n-1 的整数之和为 n(n-1)/2 这一事实。但是,您担心这会导致整数溢出并导致问题。在大多数机器上你是对的,这会导致溢出,但是(在大多数机器上)这不是问题,因为算术是使用固定精度整数完成的,通常是 32 位整数。当发生整数溢出时,得到的数字不是没有意义的。相反,它只是您计算实际结果后得到的值,然后丢弃除最低 32 位之外的所有内容。从数学上讲,这称为模运算,计算机中的运算是模 232 完成的。不过,更一般地,假设整数以某个固定 k 的模 k 存储。

幸运的是,您从普通算术中了解和喜爱的许多算术定律仍然适用于模算术。我们只需要更准确地使用我们的术语。如果 x 和 y 除以 k 时余数相同,则我们说 x 与 y 模 k 一致(表示为 x ≡k y)。这在物理机器上工作时很重要,因为当大多数硬件上发生整数溢出时,结果值与模 k 的真值一致,其中 k 取决于字长。幸运的是,以下定律适用于模算术:

例如:

  1. 如果 x ≡k y 和 w ≡k z,则 x + w ≡k y + z
  2. 如果 x ≡k y 和 w ≡k z,则 xw ≡k yz。

这意味着如果您想通过查找数组元素的总和并减去预期的总数来计算重复值,即使存在整数溢出,一切都会正常进行,因为标准算术仍然会产生硬件中的相同值(模 k)。也就是说,您也可以使用基于 XOR 的方法,它根本不需要考虑溢出。 :-)

如果你不能保证一个元素是重复的,但你可以修改元素数组,那么有一个漂亮的算法可以找到重复的值。 This earlier SO question 描述了如何实现这一点。直观地说,您可以尝试使用bucket sort 对序列进行排序,其中元素数组本身也被回收以保存存储桶的空间。

如果你不能保证一个元素是重复的,并且你不能修改元素的数组,那么问题就更难了。这是一个经典的(而且很难!)面试问题,据说 Don Knuth 花了 24 小时才解决。诀窍是将问题简化为cycle-finding 的实例,方法是将数组视为从数字 1-n 到 1-(n-1) 的函数,然后查找该函数的两个输入。然而,生成的算法,称为Floyd's cycle-finding algorithm,非常漂亮和简单。有趣的是,它与您在线性时间和恒定空间中检测链表中的循环所使用的算法相同。我建议您查一下,因为它会定期出现在软件面试中。

有关算法的完整描述以及分析、正确性证明和 Python 实现,请查看解决问题的 this implementation

希望这会有所帮助!

【讨论】:

  • 一个有趣的注释:xor 是唯一具有这些属性的函数(直到同构)。换句话说,每个非恒等元素都具有 2 阶的可数无限群是同构的。阶为 n 且每个非恒等元素阶数为 2 的有限群是同构的。
  • @ChaoXu- 你有参考资料吗?另外,为什么证明不适用于不可数无限集?
  • 对于有限情况,使用有限阿贝尔群的基本定理,我们有所有有限群,其中每个非恒等元素的 2 阶非恒等元对于某些 n 同构于 (Z_2)^n,并且 Z_2 中的 + 是与异或相同。 (这表明这些组的顺序也必须是 2^n)。对于可数无穷大的情况,我使用分组演示写了一个证明:chaoxuprime.com/2011/06/…
  • @templatetypedef 是的,它也可以推广到更高的基数。 math.stackexchange.com/questions/17054/…
  • 这么漂亮的答案。谁能帮我理解那个模算术段落?
【解决方案2】:

添加元素非常好,您只需在计算元素总和和预期总和时取中间聚合的 mod(%) 即可。对于 mod 操作,您可以使用 2n 之类的东西。您还必须在减法后修复该值。

【讨论】:

  • 你能详细说明一下吗?我不熟悉这个解决方案,也不能完全告诉你要做什么。您能否发布更详细的算法和正确性证明?
  • 这是一个在线算法。我正在使用 OP 描述的元素总和解决方案,只使用模算术,所以没有溢出。你知道从 1 到 n-1 的数字之和。该数组包含 n 个数字,一个元素重复,因此只需将它们的总和减去 1->n-1 即可得到重复的数字。
  • 啊,错过了“只有一个”部分,并认为这是针对更一般的“一些元素重复”的情况。
猜你喜欢
  • 2011-04-22
  • 2011-06-06
  • 2017-08-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-03-13
  • 1970-01-01
相关资源
最近更新 更多