我最近在某个地方遇到了一个问题:
假设您有一个1001个整数的数组。整数按随机顺序排列,但是您知道每个整数都在1到1000(含)之间。此外,每个数字在数组中仅出现一次,但一个数字出现两次。假设您只能访问一次数组的每个元素。描述找到重复数字的算法。如果在算法中使用了辅助存储,是否可以找到不需要它的算法?
我有兴趣知道的是第二部分,即不使用辅助存储。你有什么主意吗?
我最近在某个地方遇到了一个问题:
假设您有一个1001个整数的数组。整数按随机顺序排列,但是您知道每个整数都在1到1000(含)之间。此外,每个数字在数组中仅出现一次,但一个数字出现两次。假设您只能访问一次数组的每个元素。描述找到重复数字的算法。如果在算法中使用了辅助存储,是否可以找到不需要它的算法?
我有兴趣知道的是第二部分,即不使用辅助存储。你有什么主意吗?
Answers:
只需将它们加起来,然后减去如果只使用1001个数字,便可以得到的总数。
例如:
Input: 1,2,3,2,4 => 12
Expected: 1,2,3,4 => 10
Input - Expected => 2
更新2:有人认为使用XOR查找重复的号码是一个hack或trick俩。我的官方答复是:“我不是在寻找重复的数字,而是在比特集的数组中寻找重复的模式。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寄存器)来存储当前元素的索引。
除了这一点之外,这是一种破坏性算法,可以安全地扩展到任意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
Franci Penov提出的解决方案的非破坏性版本。
这可以通过使用XOR
运算符来完成。
假设我们有一个size数组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
基于解决方案的解决方案:)
这将使用附加变量,因此不能完全满足问题中的要求。
{ 1043, 1042, 1044, 1042 }
通过XOR-ing与{ 0, 1042, 1043, 1044 }
。
将所有数字加在一起。最终的总和将是1 + 2 + ... + 1000+重复的数字。
解释弗朗西斯·佩诺夫的解决方案。
(通常)问题是:给定一个任意长度的整数数组,该数组仅包含重复了偶数次的元素,但一个值重复了奇数次,则找出该值。
解决方案是:
acc = 0
for i in array: acc = acc ^ i
您当前的问题是适应。诀窍是要找到重复两次的元素,因此您需要调整解决方案以弥补这一怪癖。
acc = 0
for i in len(array): acc = acc ^ i ^ array[i]
最终,弗朗西斯的解决方案做到了这一点,尽管它破坏了整个数组(顺便说一句,它只能破坏第一个或最后一个元素...)
但是由于索引需要额外的存储空间,因此如果您还使用额外的整数,我认为您会被原谅的。限制很可能是因为它们希望阻止您使用数组。
如果他们有所需的O(1)
空间,这将被更准确地表述(因为这里是任意的,因此可以将其视为N可以是1000)。
如果您知道我们的确切数字是1-1000,则可以将结果相加并从总数中减去500500
(sum(1, 1000)
)。这将给出重复的数字,因为sum(array) = sum(1, 1000) + repeated number
。
arr = [1,3,2,4,2]
print reduce(lambda acc, (i, x): acc ^ i ^ x, enumerate(arr), 0)
# -> 2
在@Matthieu M.的答案中说明了其工作原理。
好吧,有一种非常简单的方法可以执行此操作... 1到1000之间的每个数字只发生一次,除了重复的数字....因此,从1 .... 1000开始的总和为500500。因此,算法为:
总和= 0 对于数组的每个元素: sum + =数组的那个元素 number_that_occurred_twice =总和-500500
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;
}
没有额外的存储要求(除了循环变量)。
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 )
);
length
使其具有通用性。
参数和调用堆栈是否算作辅助存储?
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);
三角数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
基于对连续值进行异或运算的性质来改进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;
}
或在伪代码/数学lang 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
在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;
}