题目描述:
在一个长度为n+1的数组里的所有的数字都在1~n的范围内,所以数组中至少有一个数字是重复的请找出数组中任意一个重复的数字,但不能修改输入的数组。例如如果长度为8的数组{2,3,5,4,3,2,6,7},那么对应的输出是重复的数字2或3。
解题思路:
要求不修改输入的数组,那么上一篇剑指offer——数组中重复的数字中的算法便不能使用了。
当然还是可以使用打表的方法做,这需要O(n)的空间复杂度。
如果要求O(1)的空间复杂度,可以考虑下面这种算法:
因为题目中所有数字都在1~n的范围内,我们可以把1~n从中间分为两部分,前一部分是[1,m],后一部分是[m+1,n],
然后统计数组中在[1,m]中的数字的个数,如果个数大于m说明重复的数字位于[1,m],否则位于[m+1,n],
然后缩小范围,继续这样做,有点类似二分,直到这个范围起始点和终点相等时,如果位于这个范围的数个数大于1,那么重复的数字就是这个起始点的值。否则没有重复。
这种算法的时间复杂度是二分查找时的O(logn),每次查找统计个数的O(n),总的时间复杂度是O(nlogn),空间复杂度是O(1)。
注意:
在一开始大致浏览书上的算法后自己实现时犯了一个错误:
在下面代码中注释掉的代码,注意两者的区别。
这个算法有一个缺陷,不能确保一定能找到重复的数字。
比如{ 3, 3, 5, 4, 3, 6, 7 } ,因为重复数字在3,而且[1,4]范围内的数字虽然正好是4个但是由重复,这时这个算法就失效了。
int counter(const int* numbers, int length, int start, int end)
{
int cnt=0;
for(int i=0;i<length;i++)
{
if(numbers[i]>=start&&numbers[i]<=end) cnt++;
}
return cnt;
}
int getDuplication(const int* numbers, int length)
{
if(numbers == nullptr || length <= 0) return -1;
int start = 1;
int end = length-1;
while(start <= end)
{
int mid =(start+end)/2;
int cnt = counter(numbers,length,start,mid);//统计数组中>=start&&<=mid的数字个数
if(start == end)
{
if(cnt > 1) return start;
else break;
}
// if(cnt <= mid) start = mid+1;
// else end = mid;
if(cnt> (mid-start+1)) end = mid; //表示重复的数字在[start,mid]
else start = mid + 1;//表示重复的数字在[mid+1,end]
}
return -1;
}