如何在洗牌的连续整数数组中找到重复元素?
Posted
技术标签:
【中文标题】如何在洗牌的连续整数数组中找到重复元素?【英文标题】:How to find a duplicate element in an array of shuffled consecutive integers? 【发布时间】:2011-02-06 00:56:54 【问题描述】:我最近在某个地方遇到了一个问题:
假设您有一个包含 1001 个整数的数组。整数是随机顺序的,但您知道每个整数都在 1 到 1000(含)之间。此外,每个数字在数组中只出现一次,除了一个数字出现两次。假设您只能访问数组的每个元素一次。描述一个算法来找到重复的数字。如果你在算法中使用了辅助存储,你能找到不需要它的算法吗?
我有兴趣了解的是第二部分,即不使用辅助存储。你有什么想法吗?
【问题讨论】:
很确定以前有人问过这个问题,但找不到确切的 qn。序列中n个整数和重复整数x的总和将是x + n(n-1)/2。 您能否将问题标题更改为更具描述性的内容?也许“查找具有特殊约束的重复数组元素” 略有不同的问题相同的答案:***.com/questions/35185/… 再次:***.com/questions/1089987/… 复制:***.com/questions/555744/… 【参考方案1】:只需将它们全部加起来,然后从中减去仅使用 1001 个数字时的总和。
例如:
Input: 1,2,3,2,4 => 12
Expected: 1,2,3,4 => 10
Input - Expected => 2
【讨论】:
@Brian Rasmussen:额外的存储空间在哪里? @leppie:保存计算的总和,但老实说,我不知道 OP 所指的额外存储究竟是什么意思。无论如何,我喜欢你的回答。 @Brian,面试官的意思可能是“不要使用哈希表或数组”......我很确定 O(1) 存储,尤其是单个变量,会令人满意。 该方法运行良好。但这个例子应该是这样的 (1,3,2,4,2=>12) - (1+2+3+4 => 10) = 2 @Franci Penov:我不确定面试问题是否应该扩大 :)【参考方案2】:更新 2: 有人认为使用 XOR 查找重复号码是一种 hack 或技巧。我的官方回应是:“我不是在寻找重复的数字,我在寻找位集数组中的重复模式。XOR 绝对比 ADD 更适合操作位集”。 :-)
更新:只是为了睡前的乐趣,这里有一个“单行”替代解决方案,它需要零额外存储(甚至不需要循环计数器),只接触每个数组元素一次,是非破坏性且根本不扩展:-)
printf("Answer : %d\n",
array[0] ^
array[1] ^
array[2] ^
// continue typing...
array[999] ^
array[1000] ^
1 ^
2 ^
// continue typing...
999^
1000
);
请注意,编译器实际上会在编译时计算该表达式的后半部分,因此“算法”将执行 1002 次操作。
如果在编译时也知道数组元素的值,编译器会将整个语句优化为一个常量。 :-)
原解:虽然能找到正确答案,但不符合问题的严格要求。它使用一个额外的整数来保存循环计数器,并且它访问每个数组元素 3 次 - 在当前迭代中读取和写入两次,在下一次迭代中读取一次。
好吧,您至少需要一个额外的变量(或 CPU 寄存器)来存储您遍历数组时当前元素的索引。
不过,除此之外,这里还有一种破坏性算法,可以安全地扩展到最多 MAX_INT 的任何 N。
for (int i = 1; i < 1001; i++)
array[i] = array[i] ^ array[i-1] ^ i;
printf("Answer : %d\n", array[1000]);
我将通过一个简单的提示将弄清楚为什么这会起作用的练习留给你:-):
a ^ a = 0
0 ^ a = a
【讨论】:
一种非破坏性的方法是在侧面维护一个蓄能器......我认为这也会使其更具可读性。 @Matthiey M. - 但非破坏性解决方案需要额外的存储空间,因此违反了问题的要求。 @Dennis Zickefoose - 我并不是说带有额外整数变量的非破坏性解决方案并不好。 :-) 但它确实违反了问题要求,这就是我选择破坏性算法的原因。至于循环计数器 - 没有办法避免这个,它是隐式允许的,因为问题表明允许代码遍历数组一次,如果没有循环计数器,这是不可能的。 @Pavel Shved - XOR 没有技巧,它是一种具有众所周知属性的数学运算,就像加法、乘法和其他一样。 @Pavel - 另外,你和我以不同的方式看待问题 - 因为我不是在搜索重复的数字,而是在一组标志中搜索重复的模式。当你以这种方式陈述问题时,使用加法现在变成了“肮脏的把戏”:-)【参考方案3】:Franci Penov 的非破坏性解决方案。
这可以通过使用XOR
运算符来完成。
假设我们有一个大小为5
:4, 3, 1, 2, 2
的数组
哪些在索引中: 0, 1, 2, 3, 4
现在对所有元素和所有索引执行XOR
。我们得到2
,它是重复元素。发生这种情况是因为,0
在 XORing 中没有任何作用。剩余的n-1
索引与数组中的相同n-1
元素配对,并且数组中唯一未配对的元素 将是重复的。
int i;
int dupe = 0;
for(i = 0; i < N; i++)
dupe = dupe ^ arr[i] ^ i;
// dupe has the duplicate.
这个解决方案的最大特点是它不会出现在基于加法的解决方案中出现的溢出问题。
由于这是一道面试题,最好先从基于加法的解决方案开始,确定溢出限制,然后给出基于XOR
的解决方案:)
这使用了一个额外的变量,所以不完全满足问题的要求。
【讨论】:
坦率地说,我没有得到这些基于 XOR 的解决方案。基本上,我们试图将“索引”与元素的值相匹配。如果匹配,结果将为零,对于重复值,异或结果将非零。对于一个简单的数组 --> 1,2,2 我们将 xor 1(元素值)^1(index)^0 (previous xor result) --> 0; 2^2^0 --> 0; 3^2^0 --> 1。这里 1 是根据 XOR 解决方案的最终结果值。除非我遗漏了一些非常明显的东西,否则我看不出这是如何有效的答案。 @codaddict 我认为循环应该从 i 初始化为 1 开始。 @codaddict +1 用于清晰的说明并提及溢出(也用于非破坏性)。即使整数有偏移量,也可以进行一些更改,例如 1043, 1042, 1044, 1042
通过 XOR-ing 与 0, 1042, 1043, 1044
。【参考方案4】:
将所有数字加在一起。最终的总和将是 1+2+...+1000+重复数字。
【讨论】:
【参考方案5】:套用弗朗西斯·佩诺夫的解决方案。
(通常的)问题是:给定一个任意长度的整数数组,其中只包含重复偶数次的元素,除了一个重复奇数次的值,找出这个值。
解决办法是:
acc = 0
for i in array: acc = acc ^ i
您当前的问题是适应。诀窍是你要找到重复两次的元素,所以你需要调整解决方案来弥补这个怪癖。
acc = 0
for i in len(array): acc = acc ^ i ^ array[i]
弗朗西斯的解决方案最终做了什么,虽然它破坏了整个数组(顺便说一句,它只能破坏第一个或最后一个元素......)
但是由于索引需要额外的存储空间,我认为如果你还使用额外的整数,你会被原谅......这个限制很可能是因为他们想阻止你使用数组。
如果他们需要O(1)
空格(1000 可以被视为 N,因为它在这里是任意的),它的措辞会更准确。
【讨论】:
我已经根据您的回答***.com/questions/2605766/…发布了 Python one-liner【参考方案6】:添加所有数字。整数 1..1000 的和为 (1000*1001)/2。与你得到的不同的是你的号码。
【讨论】:
【参考方案7】:Python中的一行解决方案
arr = [1,3,2,4,2]
print reduce(lambda acc, (i, x): acc ^ i ^ x, enumerate(arr), 0)
# -> 2
@Matthieu M.'s answer 中解释了它的工作原理。
【讨论】:
+1,干得好:尽管它不是代码高尔夫,但使用 python 的内置循环更快:)【参考方案8】:如果您知道我们有 1-1000 的确切数字,您可以将结果相加并从总数中减去 500500
(sum(1, 1000)
)。这将给出重复的数字,因为sum(array) = sum(1, 1000) + repeated number
。
【讨论】:
【参考方案9】:嗯,有一个非常简单的方法可以做到这一点...... 1 到 1000 之间的每个数字都只出现一次,除了重复的数字......所以,1....1000 的总和是500500。所以,算法是:
总和 = 0 对于数组的每个元素: sum += 数组的那个元素 number_that_occurred_twice = sum - 500500【讨论】:
【参考方案10】:n = 1000
s = sum(GivenList)
r = str(n/2)
duplicate = int( r + r ) - s
【讨论】:
【参考方案11】:public static void main(String[] args)
int start = 1;
int end = 10;
int arr[] = 1, 2, 3, 4, 4, 5, 6, 7, 8, 9, 10;
System.out.println(findDuplicate(arr, start, end));
static int findDuplicate(int arr[], int start, int end)
int sumAll = 0;
for(int i = start; i <= end; i++)
sumAll += i;
System.out.println(sumAll);
int sumArrElem = 0;
for(int e : arr)
sumArrElem += e;
System.out.println(sumArrElem);
return sumArrElem - sumAll;
【讨论】:
【参考方案12】:没有额外的存储要求(除了循环变量)。
int length = (sizeof array) / (sizeof array[0]);
for(int i = 1; i < length; i++)
array[0] += array[i];
printf(
"Answer : %d\n",
( array[0] - (length * (length + 1)) / 2 )
);
【讨论】:
您假设数组已排序。错误的假设。 @leppie:怎么会?我没有假设任何事情。它实际上像其他答案所暗示的那样使用了任何额外的空间。 虽然我对前提有疑问。它需要额外的两个整数。 @Dennis:井循环变量必须存在,length
才能使其通用。
@nvl:即使有这样的学术练习,保存单个变量的破坏性算法也不是特别有益。【参考方案13】:
参数和调用堆栈算作辅助存储吗?
int sumRemaining(int* remaining, int count)
if (!count)
return 0;
return remaining[0] + sumRemaining(remaining + 1, count - 1);
printf("duplicate is %d", sumRemaining(array, 1001) - 500500);
编辑:尾调用版本
int sumRemaining(int* remaining, int count, int sumSoFar)
if (!count)
return sumSoFar;
return sumRemaining(remaining + 1, count - 1, sumSoFar + remaining[0]);
printf("duplicate is %d", sumRemaining(array, 1001, 0) - 500500);
【讨论】:
这需要线性堆栈空间,所以这绝对是作弊。 抛出另一个参数,你可以尾调用优化它。【参考方案14】:public int duplicateNumber(int[] A)
int count = 0;
for(int k = 0; k < A.Length; k++)
count += A[k];
return count - (A.Length * (A.Length - 1) >> 1);
【讨论】:
【参考方案15】:三角形数 T(n) 是从 1 到 n 的 n 个自然数之和。它可以表示为n(n+1)/2。因此,知道在给定的 1001 个自然数中,只有一个数字重复,您可以轻松地将所有给定数字相加并减去 T(1000)。结果将包含此副本。
对于一个三角数 T(n),如果 n 是 10 的任意一次幂,也有一个漂亮的方法可以找到这个 T(n),基于 base-10 表示:
n = 1000
s = sum(GivenList)
r = str(n/2)
duplicate = int( r + r ) - s
【讨论】:
【参考方案16】:我支持添加所有元素,然后从中减去所有索引的总和,但如果元素数量非常大,这将不起作用。 IE。会导致整数溢出!所以我设计了这个算法,它可能会在很大程度上减少整数溢出的机会。
for i=0 to n-1
begin:
diff = a[i]-i;
dup = dup + diff;
end
// where dup is the duplicate element..
但是通过这种方法,我将无法找出重复元素所在的索引!
为此,我需要再次遍历数组,这是不可取的。
【讨论】:
简单的总和实际上可以工作。整数溢出不是问题,只要计算总和的变量是无符号的。【参考方案17】:基于 XORing 连续值的属性改进 Fraci 的答案:
int result = xor_sum(N);
for (i = 0; i < N+1; i++)
result = result ^ array[i];
地点:
// Compute (((1 xor 2) xor 3) .. xor value)
int xor_sum(int value)
int modulo = x % 4;
if (modulo == 0)
return value;
else if (modulo == 1)
return 1;
else if (modulo == 2)
return i + 1;
else
return 0;
或者在伪代码/数学语言 f(n) 中定义为(优化):
if n mod 4 = 0 then X = n
if n mod 4 = 1 then X = 1
if n mod 4 = 2 then X = n+1
if n mod 4 = 3 then X = 0
而在规范形式中,f(n) 是:
f(0) = 0
f(n) = f(n-1) xor n
【讨论】:
【参考方案18】:我对问题 2 的回答:
求从 1 到(到)N 的数字的总和和乘积,例如 SUM
、PROD
。
求 1 - N- x -y 的数字的总和和乘积,(假设缺少 x,y),比如说 mySum,myProd,
因此:
SUM = mySum + x + y;
PROD = myProd* x*y;
因此:
x*y = PROD/myProd; x+y = SUM - mySum;
如果解这个方程,我们可以找到 x,y。
【讨论】:
【参考方案19】:在 aux 版本中,首先将所有值设置为 -1,然后在迭代时检查是否已将值插入到 aux 数组中。如果不是(那么值必须为 -1),则插入。如果您有重复,这是您的解决方案!
在没有 aux 的情况下,您从列表中检索一个元素并检查列表的其余部分是否包含该值。如果它包含,在这里你已经找到了。
private static int findDuplicated(int[] array)
if (array == null || array.length < 2)
System.out.println("invalid");
return -1;
int[] checker = new int[array.length];
Arrays.fill(checker, -1);
for (int i = 0; i < array.length; i++)
int value = array[i];
int checked = checker[value];
if (checked == -1)
checker[value] = value;
else
return value;
return -1;
private static int findDuplicatedWithoutAux(int[] array)
if (array == null || array.length < 2)
System.out.println("invalid");
return -1;
for (int i = 0; i < array.length; i++)
int value = array[i];
for (int j = i + 1; j < array.length; j++)
int toCompare = array[j];
if (value == toCompare)
return array[i];
return -1;
【讨论】:
以上是关于如何在洗牌的连续整数数组中找到重复元素?的主要内容,如果未能解决你的问题,请参考以下文章
如何在 C# 中使用 Random 类对数组进行洗牌 [重复]
从整数数组中找到大小为 K 的连续子数组,使得从 1 到 k 的附加元素永远不会低于零