题目:
在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字2。
这道题乍一看挺简单的,只需要找出数组中重复的任意一个数字即可,最简单的办法就是直接排个序然后遍历有序数组,如果两个相邻的数字一样那就是重复的数字了,时间复杂度O(nlogn),空间复杂度O(1)。另外一种思考起来也很简单的办法就是额外开辟一个哈希表来存储每个数字出现的次数,如果已经出现过那就是重复的数字,这个方法也可以用一个新的全为false的bool数组count来实现,只需要遍历一次原始数组numbers,把原始数组中的数字(numbers[i])作为新数组的下标,把这个元素(count[numbers[i]])设置成true。如果在遍历时已经发现count[numbers[i]]为true的话说明它已经出现过了。代码如下,时间复杂度O(n),空间复杂度O(n):
class Solution {
public:
// Parameters:
// numbers: an array of integers
// length: the length of array numbers
// duplication: (Output) the duplicated number in the array number
// Return value: true if the input is valid, and there are some duplications in the array number
// otherwise false
bool duplicate(int numbers[], int length, int* duplication) {
if (numbers == NULL || length <= 0) {
return false;
}
for (int i = 0; i < length; i++) {
if (numbers[i] < 0 || numbers[i] > length - 1) {
return false;
}
}
bool* count = new bool[length];
for (int i = 0; i < length; i++) {
count[i] = false;
}
for (int i = 0; i < length; i++) {
if (count[numbers[i]]) {
*duplication = numbers[i];
return true;
}
count[numbers[i]] = true;
}
return false;
}
};
剑指上提出,如果不额外开辟空间,想要达到O(n)的效果,可以考虑到数组的特性。因为整个数组长度为n,数字范围也在0到n-1之间,所以如果没有重复数字的话的情况下可以每个数字占一位,并且可以交换成每个数字就占据它自己作为下标的哪一位。但是由于有重复数字,所以如果让它们都乖乖回到自己该在的地方的话总会有两个数字要挤一个位置的,这样的话就找出了重复的数字。具体操作大概是这样的:从头开始遍历数组,比较当前的数字numbers[i]是否等于i,如果是就接着遍历下一个数字,否则就把numbers[i]和它该去的地方的数字进行交换,直至当前的numbers[i]就是i为止。而如果哪次想要交换的时候发现它该去的地方已经有和它一样的数字了,那么这个数字就是重复的数字啦!这个方法的时间复杂度是O(n),空间复杂度O(1),但是改变了数组:
class Solution {
public:
// Parameters:
// numbers: an array of integers
// length: the length of array numbers
// duplication: (Output) the duplicated number in the array number
// Return value: true if the input is valid, and there are some duplications in the array number
// otherwise false
bool duplicate(int numbers[], int length, int* duplication) {
if (numbers == NULL || length <= 0) {
return false;
}
for (int i = 0; i < length; i++) {
if (numbers[i] < 0 || numbers[i] > length - 1) {
return false;
}
}
for (int i = 0; i < length; i++) {
while (numbers[i] != i) {
if (numbers[i] != numbers[numbers[i]]) {
swap(numbers[i], numbers[numbers[i]]);
}
else {
*duplication = numbers[i];
return true;
}
}
}
return false;
}
};
在这道题的后面,又提出了一个和这个很类似的题目,但是要求不能改变数组的顺序:
在一个长度为n+1的数组里的所有数字都在1~n的范围内,所以数组中至少有一个数字是重复的。请找出数组中任意一个重复的数字,但不能修改输入的数组。例如,如果输入长度为8的数组{2, 3, 5, 4, 3, 2, 6, 7},那么对应的输出是重复的数字2或3。
这个变体也可以采用额外空间的方法来完成,但如果不采用额外空间,可以使用二分法来完成,这样的时间复杂度就变成了O(nlogn)。这个解法非常巧妙:比如上面给出的数组长度为8,那么我们就把1~7分成两半,1~4和5~7,分别统计1~4这四个数字在数组中出现的次数,和5~7在数组中出现的次数,如果出现的次数超过了分段的长度(比如1~4出现了5次),那么重复的数字就一定在1~4中,那么就接着再把1~4分成两半,1~2和3~4,前半段在数组中出现了两次(虽然两次都是2),后半段出现了三次,那么肯定就在后半段出现重复,继续分别统计3和4的出现次数,发现是3。这种情况下就没办法找出所有的重复数字(比如2),因为这会受到其他没有出现的数字的影响。代码就懒得写了orz