我最近在某个地方遇到了一个问题:
假设您有一个1001个整数的数组。整数是随机顺序,但您知道每个整数在1到1000之间(包括1和1000)。此外,每个数字在数组中只出现一次,但一个数字除外,它出现两次。假设您只能访问数组的每个元素一次。描述一个算法来查找重复的数字。如果您在算法中使用了辅助存储,是否可以找到不需要它的算法?
我感兴趣的是第二部分,即不使用辅助存储。你有什么想法吗?
答案 0 :(得分:104)
只需将它们全部添加,如果只使用了1001个数字,则减去所需的总数。
例如:
Input: 1,2,3,2,4 => 12
Expected: 1,2,3,4 => 10
Input - Expected => 2
答案 1 :(得分:77)
更新2:有些人认为使用XOR查找重复的数字是一个黑客或技巧。我的官方回应是:“我不是在寻找一个重复的数字,我正在寻找一组位集中的重复模式。而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次操作中执行。
如果在编译时也知道数组元素值,编译器会将整个语句优化为常量。 : - )
原始解决方案:哪些不符合问题的严格要求,即使它能找到正确的答案。它使用一个额外的整数来保持循环计数器,并且它访问每个数组元素三次 - 两次读取它并在当前迭代中写入它并且一次读取它以用于下一次迭代。
嗯,你需要至少一个额外的变量(或CPU寄存器)来存储当前元素的索引。
除此之外,这是一个破坏性算法,可以安全地扩展任何N到MAX_INT。
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
答案 2 :(得分:22)
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
的解决方案:)
这使用了一个额外的变量,因此完全不符合问题中的要求。
答案 3 :(得分:15)
将所有数字加在一起。最终总和将是1 + 2 + ... + 1000 +重复数字。
答案 4 :(得分:6)
用Francis Penov的解释来解释。
(通常)问题是:给定一个任意长度的整数数组,只包含重复偶数次的元素,除了一个重复奇数倍的值,找出这个值。
解决方案是:
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,因为它在这里是任意的),它会被更准确地表达。
答案 5 :(得分:5)
添加所有号码。整数1..1000的总和是(1000 * 1001)/ 2。与你得到的不同的是你的数字。
答案 6 :(得分:3)
如果您知道我们的确切数字为1-1000,您可以将结果相加并从总数中减去500500
(sum(1, 1000)
)。这将给出重复的数字,因为sum(array) = sum(1, 1000) + repeated number
。
答案 7 :(得分:2)
嗯,有一种非常简单的方法可以做到这一点...... 1到1000之间的每个数字都只发生一次,除了重复的数字....所以,总和从1到1000之间是500500.所以,算法是:
sum = 0 for each element of the array: sum += that element of the array number_that_occurred_twice = sum - 500500
答案 8 :(得分:2)
arr = [1,3,2,4,2]
print reduce(lambda acc, (i, x): acc ^ i ^ x, enumerate(arr), 0)
# -> 2
有关其工作原理的说明在@Matthieu M.'s answer。
答案 9 :(得分:1)
没有额外的存储要求(除了循环变量)。
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 )
);
答案 10 :(得分:1)
参数和callstacks是否算作辅助存储?
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);
答案 11 :(得分:1)
n = 1000
s = sum(GivenList)
r = str(n/2)
duplicate = int( r + r ) - s
答案 12 :(得分:1)
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;
}
答案 13 :(得分:1)
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);
}
答案 14 :(得分:0)
三角形数T(n)是从1到n的n个自然数之和。它可以表示为n(n + 1)/ 2。因此,知道在给定的1001个自然数中,只有一个且只有一个数字是重复的,您可以轻松地对所有给定数字求和并减去T(1000)。结果将包含此副本。
对于三角数T(n),如果n是10的任何幂,那么根据基数10表示,还有一种漂亮的方法找到这个T(n):
n = 1000
s = sum(GivenList)
r = str(n/2)
duplicate = int( r + r ) - s
答案 15 :(得分:0)
根据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
答案 16 :(得分:0)
我支持添加所有元素,然后从中减去所有索引的总和,但如果元素的数量非常大,这将不起作用。即它会导致整数溢出!所以我设计了这个算法,这可能会在很大程度上减少整数溢出的可能性。
for i=0 to n-1
begin:
diff = a[i]-i;
dup = dup + diff;
end
// where dup is the duplicate element..
但是通过这种方法,我将无法找到重复元素存在的索引!
为此,我需要再次遍历数组,这是不可取的。
答案 17 :(得分:0)
我对问题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。
答案 18 :(得分:0)
在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;
}