剑指 Offer 03. 数组中重复的数字
找出数组中重复的数字。 在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。
示例 1:
输入:
[2, 3, 1, 0, 2, 5, 3]
输出:2 或 3
限制:
2 <= n <= 100000
思路分析:排序+查找
题目显而易见的一个解决方法是对该数组进行排序,然后在排序的数组中查找出重复元素就很容易了,至于排序的方法,为了熟悉一下快排,因此在这里我使用的是手写的快排。具体见下面的解法一。
C++代码:
class Solution {
public:
int findRepeatNumber(vector<int>& nums) {
int n = nums.size();
//1.排序
//也可以用库函数
//sort(nums.begin(),nums.end());
quickSort(nums,0,n-1);
//2.查找
for(int i=0;i<n;i++)
{
if(nums[i]==nums[i+1])
{
return nums[i];
}
}
return -1;
}
//快排
void quickSort(vector<int>&a,int left,int right)
{
//特判
if(left>=right) return;
int i = left-1;
int j = right+1;
int x = a[left+right>>1];
while(i<j)
{
do i++;while(a[i]<x);
do j--;while(a[j]>x);
if(i<j)
{
swap(a[i],a[j]);
}
}
//递归左边部分
quickSort(a,left,j);
//递归右边部分
quickSort(a,j+1,right);
}
};
复杂度:
时间:排序一个长度为n的数组需要 O(nlogn) 的时间。
空间:常数空间 O(1)
思路分析:(优化)哈希表
提到
重复
二字,必然少不了哈希表。具体的,遍历整个数组,每次扫描数组中每一个值,可以判断其有没有在哈希表中,如果在,直接返回,否则,只需要更新一下哈希表状态即可。
C++代码:
class Solution {
public:
int findRepeatNumber(vector<int>& nums) {
int n = nums.size();
//开一个哈希表
unordered_map<int,bool> mp;
//遍历nums数组
for(int i:nums)
{
//如果该值已存在,直接返回
if(mp[i])
{
return i;
}
//更新一下哈希表的状态
mp[i] = true;
}
return -1;
}
};
复杂度:
时间:由于哈希表每次判断只需要O(1)的时间,长度为n的数组,因此复杂度为O(n)。
空间:毫无疑问,这种解法使得在时间上得到了优化,同时却是以牺牲O(n)的哈希表空间换取的,所以空间复杂度为O(n)
思路分析:(再优化)原地交换
原地交换??
在具体的题目中,有的解题方法是需要我们对题目已经其数据进行观察,找到规律进而得到一条
捷径
,这里不妨以提供的用例:[2, 3, 1, 0, 2, 5, 3]
配合图解来加以论述该方法的可行性。很容易观察到,如果对没有重复数字的数组进行排序,那么排序之后的数字
i
将会出现在下标为i
的位置。复现一下这个重排的大致过程:
扫一遍数组,到下标为
i
时,首先判断一下该处的值(假如为v
)是不是等于i
。如果答案是肯定的,那么接着往下扫描下一个数。
如果不是,再拿它和第
v
个数字进行比较,如果此时它(这里指的是i
)和v
相等,说明找到了一个重复数字。如果它和第
v
个数字不相等,就将把第i
个数和第v
个数进行交换,也就是把v
放到原本属于它的位置。重复以上比较、交换的过程,直到程序结束。
有点懵??再来亿遍,用例模拟。
还是上面的官方给的测试用例:
[2, 3, 1, 0, 2, 5, 3]
第一步:
i=0
,判断:nums[0]==2!=0
交换:swap(nums[0],nums[nums[0]])
即是交换(2,1)
的位置。结果:
[1, 3, 2, 0, 2, 5, 3]
满足条件【nums[i]不等于i
】i
不变,继续扫描。
第二步:
i=0
,判断:nums[0]==1!=0
交换:swap(nums[0],nums[nums[0]])
即是交换(1,3)
的位置。结果:
[3, 1, 2, 0, 2, 5, 3]
满足条件【nums[i]不等于i
】i
不变,继续扫描。
第三步:
i=0
,判断:nums[0]==3!=0
交换:swap(nums[0],nums[nums[0]])
即是交换(3,0)
的位置。结果:
[0, 1, 2, 3, 2, 5, 3]
满足条件【nums[i]不等于i
】i
不变,继续扫描。
第四步:
i=0
,判断:nums[0]==0
满足条件【nums[i]==i
】此时[0, 1, 2, 3, 2, 5, 3]``````i++
继续下一轮比较。
第五步:
i=1
,判断:nums[1]==1
满足条件【nums[i]==i
】此时[0, 1, 2, 3, 2, 5, 3]``````i++
继续下一轮比较。
第六步:
i=2
,判断:nums[2]==2
满足条件【nums[i]==i
】此时[0, 1, 2, 3, 2, 5, 3]``````i++
继续下一轮比较。
第七步:
i=3
,判断:nums[3]==3
满足条件【nums[i]==i
】此时[0, 1, 2, 3, 2, 5, 3]``````i++
继续下一轮比较。
第八步:
i=4
,判断:nums[4]==2!=4
交换:swap(nums[4],nums[nums[4]])
即是交换(2,2)
的位置。注意,这里我们发现此时交换的数字都是2,不过前者的下标为
i=4
,而后者下标i=2
,就是说,此时我们已经找到了数组中的一个重复值,因此进行返回即可,该用例答案为2
,程序结束。
C++代码:
class Solution {
public:
int findRepeatNumber(vector<int>& nums) {
int n = nums.size();
if(n<2) return -1;
for(int i=0;i<n;i++)
{
if(nums[i]<0) return -1;
}
for(int i=0;i<n;i++)
{
while(nums[i]!=i)
{
if(nums[i]==nums[nums[i]])
{
return nums[i];
}
swap(nums[i],nums[nums[i]]);
}
}
return -1;
}
};
复杂度:
时间:O(n)。
空间:O(1)。