假设数组的整数在1到1,000,000之间。
我知道解决这个问题的一些流行方法:
我最近遇到了另一个解决方案,我需要一些帮助来理解它背后的逻辑:
保留一个基数累加器。你独占 - 或累加器 索引和该索引处的值。
x ^ C ^ x == C这一事实在这里很有用,因为每个数字都是 xor'd两次,除了在那里两次,将出现3 倍。 (x ^ x ^ x == x)最终索引,将出现一次。 因此,如果我们使用最终索引对累加器进行种子处理,则使用累加器 最终值将是列表中的数字两次。
如果有人能帮助我理解这种方法背后的逻辑,我会很感激(用一个小例子!)。
答案 0 :(得分:8)
假设你有一个累加器
int accumulator = 0;
在循环的每一步中,您使用i
和v
对累加器进行异或,其中i
是循环迭代的索引,v
是值数组的i
位置。
accumulator ^= (i ^ v)
通常情况下,i
和v
将是相同的数字,因此您最终会这样做
accumulator ^= (i ^ i)
但是i ^ i == 0
,所以这将最终成为无操作,并且累加器的值将保持不变。此时我应该说数组中数字的顺序无关紧要,因为XOR是可交换的,所以即使数组被混洗开始,结尾处的结果仍应为0
(初始值)累加器)。
现在如果一个数字在数组中出现两次怎么办?显然,这个数字在XORing中会出现三次(一个用于索引等于数字,一个用于数字的正常外观,一个用于额外外观)。此外,其他一个数字只会出现一次(仅针对其索引)。
此解决方案现在继续假设仅出现一次的数字等于数组的最后一个索引,或者换句话说:数组中的数字范围是连续的并且从要处理的第一个索引开始(编辑:感谢caf对这个单挑评论,这是我的想法,但我写完时完全搞砸了)。如果(N
只出现一次)作为给定,请考虑从
int accumulator = N;
有效地使N
在XORing中再次出现两次。此时,我们只剩下数字,它们只出现两次,只有一次出现三次。由于两次出现的数字将XOR输出为0,因此累加器的最终值将等于出现三次的数字(即一次额外)。
答案 1 :(得分:3)
介于1和10,001之间的每个数字都显示为数组索引。 (不是C数组是0索引的吗?好吧,如果我们对数组值和索引是从0开始还是从两者开始都是一致的话,它没有什么区别。我将从数组开始到1,因为这就是问题所说的。)
无论如何,是的,1到10,001之间的每个数字,恰好一次,作为数组索引出现。介于1和10,000之间的每个数字也恰好作为数组值出现一次,但重复值除外,它出现两次。从数学上讲,我们整体的计算如下:
1 xor 1 xor 2 xor 2 xor 3 xor 3 xor ... xor 10,000 xor 10,000 xor 10,001 xor D
其中D是重复值。当然,计算中的术语可能不会按顺序出现,但xor是可交换的,因此我们可以重新排列我们喜欢的术语。每个n n xor n
为0。所以上面简化为
10,001 xor D
x或10,001,你得到D,重复的值。
答案 2 :(得分:0)
逻辑是你只需存储累加器值,只需要经过一次数组。那非常聪明。
当然,这是否是实践中最好的方法取决于计算独占的工作量,以及数组的大小。如果数组中的值是随机分布的,那么使用不同的方法可能会更快,即使它使用更多内存,因为在检查整个数组之前可能很久就会发现重复值。
当然,如果数组排序开始,事情就容易多了。所以它在很大程度上取决于值如何在整个数组中分布。
答案 3 :(得分:0)
问题是:你是否有兴趣知道如何做一些与现实世界没什么关系的聪明但纯粹的学术x或技巧,或者你想知道这一点,因为在现实世界中你可以编写使用数组的程序吗?这个答案解决了后一种情况。
严肃的解决方案是遍历整个阵列并按照您的方式对其进行排序。排序时,请确保没有重复值,即实现抽象数据类型“set”。这可能需要分配第二个数组,并且排序将非常耗时。我不知道它是否比巧妙的xor技巧更耗时或更少耗时。
然而,在现实世界中, n 未排序值的数组有什么用呢?如果它们未被排序,我们必须假设它们的顺序在某种程度上很重要,因此可能必须保留原始数组。如果你想搜索原始数组或分析重复数据,中值等等,你真的想要它的排序版本。一旦你对它进行了排序,就可以用“O log n”二进制搜索它。