题目
在一个长度为n的数组里的所有数字都在0~n-1 的范围里面,数组中有一些重复的数字,但是不知道有几个数字重复了,也不知道每个数字重复了几次,题目要求:找出数组中任意一个重复的数字
这个题目有三种思路可以实现两种是通过数组实现,还有一个是通过哈希表来实现,我就把除了哈希表的那两种算法实现一下吧。
方法一
如果规定了空间复杂度为O(1),那也就是说不能开辟新的空间,我们就只能在原数组上进行操作了。前提是,允许数组进行变动。
第一种方法算法思想如下
——>因为数组元素的范围已经给定,数组的大小也已知道,那么我们可以模拟哈希表进行数组的排序,在数组下标为0的位置放上元素0,然后在1的位置放上元素1,这样如果没有重复的情况下,每个位置都放着对应的元素
——>但是问题已经说明肯定有重复的数字,所以说,如果第一次在对应的位置上放上元素之后,第二次再次遇到了这个元素,发现它在对应的位置山已经有一个数了,那么它就一定是重复的了,然后用一个指针指向这个值,返回给主函数就可以得到了
优点
这个算法有一个明显的优点,时间复杂度很小,只有O(n),因为虽然它经过的是两层循环,但是每次一个数字最多只要交换两次就可以找到它的位置,所以总的时间复杂度仍然是O(n)
空间复杂度也是很小的,只有O(1),因为它就是在本数组中操作的,没有开辟额外的空间
缺点
唯一的缺点就在于破坏了数组的结构,如果具体的面试题中要求不能破坏数组,那就不要选择这种啦
代码放在下面
int FindNumbers(int numbers[],int length)
{
int i=0;
for(i=0;i<length;i++)
{
while(numbers[i] != i)
{
if(numbers[i]== i)
{
return ture;
}
swap(numbers[i],numbers[numbers[i]]);
}
}
}
方法二
第二种方法相比较第一种能复杂一些了,第二种方法的思路和二分查找法很类似,不过,它比二分查找多了一个数组个数的计算,第二种算法的思想其实也不难,具体思路如下
第一步 -> 将数组一分为二,假设有十个数字,那么就分成1-5 和 6-10 l两段区间
第二步-> 统计1-5区间中的元素个数,如果按照上一题的思路,1-5区间中数字如果没有重复,那就应该刚好是五个数字,但是如果有重复的数字,那么它的个数应该大于5,这个时候,我们就需要把数组缩小到1-5,然后再把1-5的区间缩小为1-2和3-5,这样继续查找,直到最后剩下一个数的时候就终止查找
但是这个方法也有缺陷,它有可能第一次就会查找失败,比如说如下数组
1 2 2 4 5 6 7 8 9
这个时候,由于左边和右边数组个数都相同,就无法进行查找了
不过,使用这种方法也要依靠具体情况而定,如果考官给出的数组满足查找条件,那就放心大胆的用
优点
不会修改数组,由于是近似于二分查找,所以时间复杂度也不高,只有O(n *log n)
缺点
只有一部分情况可以满足,因此需要看具体情况使用
代码放在下面
int FindNumbers2(int a[],int length,int start,int end)
{
while(end>=start)
{
int middle=(end-start)>>1+start;
int count=CountRange(numbers,length,start,end)
if(end == start)
{
if(count > 1)
return start;
else
break;
}
if(count>middle-start+1)
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;
}
}
bool FindOne(int a[],int row,int col,int numbers)
{
while( col>=0 &&row >= 0 )
{
if(a[row-1][col-1] == numbers)
{
return true;
}
if(numbers>a[row-1][0])
++col;
else
--row;
}
return false;
}