面试题3:数组中重复的数字
题目一:
在一个长度为n的数组里的所有数字都在0~n-1的范围内。数组中某些数字是重复的,但是不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是重复的数字2或者3.
思路分析:
算法1:对数组进行排序,然后从头到尾扫描扫描排序后的数组。排序一个长度为n的数组需要O(nlogn)的时间。
bool duplicate(int numbers[], int length, int* duplication) {
if(numbers==nullptr || length<=0){
return false;
}
sort(numbers,numbers+length);// 对数组进行归并排序,复杂度为O(nlogn)
for(int i=0; i<length-1; i++){
if(numbers[i]==numbers[i+1]){
*duplication=numbers[i];
return true;
}
}
return false;
}
算法2:利用哈希表,从头到尾扫描数组的每一个元素,每扫描一个数字,都用O(1)的时间来判断哈希表里是否包含了该数字,如果哈希表里没有该数字,就把它加入哈希表,如果哈希表中已存在该数字,那么就找到了一个重复的数字。该算法的时间复杂度为O(n),代价是以一个空间为O(n)的哈希表。
代码:
bool duplicate(int numbers[], int length, int* duplication) {
if(numbers==nullptr || length<=0){
return false;
}
map<int,int> m ;
for(int i=0; i<length; i++){
m[numbers[i]]++;
}
for(int i=0; i<length; i++){
if(m[i]>1){
*duplication=i;
return true;
}
}
return false;
}
算法3:进一步改进算法,使算法的空间复杂度为O(1)。注意到数组中的数字都在0~n-1的范围内,如果数组中没有重复的数字,那么当数组排序之后数字 i 将出现在下标为 i 的位置。由于数组中有重复的数字,有些位置可能存在多个数字,有的位置可能没有数字。现在我们重新排列这个数组,从头到尾依次扫描数组中的每个数字,当扫描到下标为 i 的数字时,首先比较这个数字(用m表示)是不是等于 i 。如果是,则接着扫描下一个数字; 如果不是,则再拿它和第m个数字进行比较。如果它和第m个数字相等,就找到了一个重复的数字(该数字在下标为m和 i 的位置都出现了);如果它和第m个数字不相等,就把第 i 个数字和第 m 个数字交换,把m放到属于它的位置。接下来重复这个比较、交换的过程,直到我们发现一个重复的数字。
bool duplicate(int numbers[], int length, int* duplication) {
if(numbers==nullptr || length<=0){
return false;
}
for(int i=0; i<length; ++i){
if(numbers[i]>length-1 || numbers[i]<0){
return false;
}
}
for(int i=0; i<length; ++i){
while(numbers[i]!=i){
if(numbers[i] == numbers[numbers[i]]){
*duplication=numbers[i];
return true;
}
//swap numbers[i] and numbers[numbers[i]]
int temp=numbers[i];
numbers[i]=numbers[temp];
numbers[temp]=temp;
}
}
return false;
}
上述代码中,找到的重复值通过参数duplication传给函数的调用者,而函数的返回值表示数组中是否有重复的数字。
虽然代码中有一个两重循环,但是每个数字最多交换两次就能找到自己的位置,因此总的时间复杂度是O(n),所有操作都在原数组上操作,不需重新分配内存,因此空间复杂度为O(1)。
题目二:
不修改数组找出重复数字
在一个长度为n+1的数组里的所有数字都在1~n的范围内,所以数组中至少有一个数字是重复的。请找出数组中任意一个重复的数字,但不能修改输入的数组。例如,如果输入长度为8的数组{2,3,5,4,3,2,6,7},那么对应的输出数字应该是2或者3。
思路分析:
算法1:创建一个长度为n+1的辅助数组,然后逐一把原数组的每个元素复制到辅助数组,如果原数组中被复制的数字是m,则复制到辅助数组下标是m的位置,这样很容易发现哪个数字是重复的,但是需要O(n)的辅助空间。
bool duplicate(int numbers[], int length, int* duplication) {
if(numbers==nullptr || length<=0){
return false;
}
int *a=new int[length]; // 新建一个辅助数组a
for(int i=0; i<length; i++){
if(a[numbers[i]]==numbers[i]){
*duplication=numbers[i];
return true;
}
else{
a[numbers[i]]=numbers[i];
}
}
return false;
}
算法2:若数组中没有重复数字,那么在1 ~ n的范围内,只有n个数字。由于数组里包含超过n个数字,所以一定包含了重复的数字。我们把1 ~ n的数字从中间数字m分成两部分,前一半是1 ~ m,后一半是m+1 ~ n,如果1 ~ m的数字数目超过了m,那么前一半一定有重复的数字;否则,后一半一定包含了重复的数字,我们继续把有重复数字的区间一分为二,直到找到一个重复的数字。
bool duplicate(int numbers[], int length, int* duplication) {
if(numbers==nullptr || length<=0){
return false;
}
int start=1;
int end=length-1;
while(end>=start){
int mid=(start+end)/2;
int count=countRange(numbers, length, start, mid);
if(end==start){
if(count>1){
*duplication=start;
return true;
}
else
break;
}
if(count>(mid-start+1))
end=mid;
else
start=mid+1;
}
return false;
}
int countRange(const int* numbers, int length, int start, int end){
if(numbers==nullptr || length<=0){
return 0;
}
int count=0;
for(int i=0;i<=length;i++){
if(numbers[i]>=start && numbers[i]<=end){
count++;
}
}
return count;
}