题目描述:在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。例如:输入:[2, 3, 1, 0, 2, 5, 3],输出:2 或 3 。
- 利用哈希表解决
从头到尾按顺序扫描数组的每个数字,每扫描到一个数字的时候,都可以用O(1)的时间来判断哈希表里是否已经包含了该数字。如果已经有了就找到了一个重复的数字,若没有就把它就如哈希表。时间复杂度是O(n),空间复杂度是O(n)。
class Solution {
public:
int findRepeatNumber(vector<int>& nums) {
set<int> s;
for(int n : nums)
{
if(n < 0 || n > nums.size() - 1)
{
return -1;
}
if(s.find(n) != s.end())
return n;
s.insert(n);
}
return -1;
}
}
- 从头到尾依次扫描这个数组中的每个数字。当扫描到下标为 i 的数字的时候,首先比较这个数字(用m表示)是不是等于 i 。如果是则接着扫描下一个数字,若不是则拿它和第m个数字进行比较。如果它和第m个数字相等,就找到了一个重复的数字;如果它和第m个数字不相等,就把第 i 个数字和第m个数字交换,把m放到属于它的位置。接下来再重复这个比较、交换过程,直到发现一个重复的数字。
代码中尽管有两重循环,但每个数字最多只要交换两次就能找到属于自己的位置,所以时间复杂度为O(n),不需要额外分配内存,所以空间复杂度是O(1)。
class Solution {
public:
int findRepeatNumber(vector<int>& nums) {
if(nums.size()<1)
return -1;
for(auto n:nums)
{
if(n<0||n>nums.size()-1)
{
return -1;
}
}
for(int i=0;i<nums.size();i++)
{
//要用while循环判断,比如[3,4,2,0,0,1]
while(nums[i]!=i)
{
if(nums[i]==nums[nums[i]])
return nums[i];
int tmp=nums[i];
nums[i]=nums[tmp];
nums[tmp]=tmp;
}
}
return -1;
}
}
- 把0 ~ n-1的数字从中间的数字m分为两部分,前面一半为0 ~ m-1,后面一半为m ~ n-1。如果0 ~ m-1的数字数目超过m,那么这一半的区间里一定包含重复的数字;否则,另一半区间里一定包含重复的数字。我们可以继续把包含重复数字的区间一分为二,直到找到一个重复数字。这个过程和二分查找算法很类似,只是多了一步统计区间里数字的数目。
函数countRange将被调用log(n)次,每次需要O(n)时间,所以时间复杂度为O(nlogn),空间复杂度为O(1)。没有修改输入的数组。
class Solution {
public:
int countRange(vector<int>& nums,int s,int e)
{
if(nums.size() < 1)
return 0;
int count = 0;
for(int i = 0; i < nums.size(); i++)
{
if(nums[i] >= s && nums[i] <= e)
count++;
}
return count;
}
int findRepeatNumber(vector<int>& nums) {
int s = 0;
int e = nums.size() - 1;
while(s <= e)
{
int m = (s + e) >> 1;
int count = countRange(nums,s,m);
if(e == s)
{
if(count > 1)
{
return s;
}
else
{
break;
}
}
if(count > (m - s + 1))
e = m;
else
s = m + 1;
}
return -1;
}
};
但是这种算法不能保证找出所有数字。例如,不能找出[2,3,5,4,3,2,6,7]中重复的数字2,这是因为在1~2范围内有1和2两个数字,这个范围的数字也出现两次,此时我们用该算法不能确定是每个数字各出现一次还是某个数字出现了两次。
总结:我们首先要问面试官功能请求(找出任意一个重复的数字、找出所有重复的数字)和性能请求(时间效率优先还是空间效率优先),根据这个选取不同的算法。只是时间优先用哈希表,还有空间要求,就原地排序数组,如果面试官要求空间O(1)并且不能修改原数组,得写成二分法。