题目:
在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。
例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是重复的数字2或者3。
思路:
1、排序
然后从头到尾扫描数组
时间复杂度:O(nlogn)
2、利用哈希表
从头到尾按顺序扫描数组中的每个数字,每扫描到一个数字的时候,都可以用O(1)的时间判断哈希表里是否已经包含了该数字。如果哈希表里还没有这个数字,就把他加入哈希表,如果哈希表里已经存在该数字,就找到一个重复的数字。
时间复杂度:O(n) 空间复杂度:O(n)
3、对比下标法
由于题目要求长度为n的数组里的所有数字都在0到n-1的范围内,如果不重复,则排完序后,下标应该与其值相等。按照这个规则,找到含有重复数字数组里的重复数字。
以数组{2,3,1,0,2,5,3}为例
下标为0的数字是2,与它的下标0不相等,那么我们把它和下标为2的数字比较,如果不相等则交换,数组变为{1,3,2,0,2,5,3}
再看下标为0的数字是1,与它的下标0不相等,同样的,把它与下标为1的数字比较,如果不相等则交换,数组变为{3,1,2,0,2,5,3}
再看下标为0的数字是3,与它的下标0不相等,同样的,把它与下标为3的数字比较,如果不相等则交换,数组变为{0,1,2,3,2,5,3}
再看下标为1,2,3的数字,下标均与其值相等
下标为4的数字是2,与其下标不等,比较它和下标为2的数字,相等,那么找到一个重复数字。
时间复杂度:O(n) (因为while循环中的每个位置最多需要两次就能找到对的值), 空间复杂度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(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(i!=numbers[i])//注意需要用while循环 { if(numbers[i]!=numbers[numbers[i]]) swap(numbers,i,numbers[i]); else { *duplication=numbers[i]; return true;} } } return false; } void swap(int arr[],int i,int j) { int temp; temp=arr[i]; arr[i]=arr[j]; arr[j]=temp; } };
变形题:在不修改数组的情况下找出重复的数字
题目:在一个n+1的数组里的所有数字都在1~n的范围内,所以数组中至少有一个数字是重复的。请找出数组中任意一个重复的数字,但不能修改输入的数组。例如,如果输入长度为8的数组{2,3,5,4,3,2,6,7},那么对应的输出是重复的数字2或3.
思路:
可以使用上题的算法:现在是如果没有重复的数字,那么按照顺序排列后,数组值和下标相差1。
但题目要求不能修改原始数组,所以不能使用。
思路2:
可以创建一个长度为n+1的辅助数组,如何逐一把原数组的每个数字复制到辅助数组。如果原数组中被复制的数字是m,则把它复制到辅助数组中下标为m的位置。这样就很容易发现哪个数字是重复的。
但这需要O(n)的辅助空间
思路3:
二分查找的思路
以长度为8的数组{2,3,5,4,3,2,6,7}为例分析查找的过程。
1、根据题目要求,这个长度为8的所有数字都在1~7的范围内。中间的数字4把1~7分为两段,一段是1~4,另一段是5~7。
2、分析1~4中的数字在数组中出现的次数,一共出现了5次,因此这四个数中一定存在重复数字。
3、把1~4的范围一分为2,一段时1~2,另一段是3~4
4、分析1~2中数字出现的次数,两次故没有重复数字。再分析3~4中数字的重复次数,三次,那么3或4肯定有一个是重复数字
5、将3~4分为两段,一段是3,一段是4,分析3出现的次数是2,所以3是一个重复的数字。
时间复杂度:如果输入长度为n的数组,函数countRange将被调用O(logn)次,每次都需要O(n)的时间,因此总的时间复杂度是O(nlogn)
空间复杂度:O(1)
代码:
int getDuplication(const int* numbers,int length) { if(numbers==NULL||length<=0)//判断输入的数是否符合要求 return -1; int start=1;//因为长度为8的所有数字都在1~7的范围内,所以start=1,end=length-1 int end=length-1; while(start<=end) { int middle=start+(end-start)/2); int count=countRange(numbers,length,start,middle); if(end==start) { if(count>1) return start; else break; } if(count>(middle-start+1))//如果1~4中的数字在数组中出现了5次,因此这四个数中一定存在重复数字。 end=middle; else start=middle+1; } return -1; } /* 计算某一区间中的数在数组中出现的次数*/ int countRange(const int*numbers,int length,int start,int end) { if(numbers=NULL) return 0; int count=0; for(int i=0;i<length;i++) if(numbers[i]>=start&&numbers[i]<=end) ++count; return count; }
缺点:这个算法找不出像数组{2,3,5,4,3,2,6,7}这样的重复数字。想一想为什么呢?
因为在1-2区间内,count=2,被判断为这个范围内没有重复的数字。