Acwing 找出数组中重复的数字
题库13.找出数组中重复的数字
题目内容如下:
分析:很容易想到,要找重复的数字,不就是要我去统计每个数字出现的次数咩!因为是整数数组,且数字范围在0~n-1,还可以直接将数字作为下标,cnt[nums[i]]就对应了nums[i]这个数字出现的次数,只需要遍历一遍数组即可,时间复杂度是O(n),但是需要一个额外的数组cnt[n],增加了空间复杂度O(n),代码就是这样的:
class Solution {
public:
int duplicateInArray(vector<int>& nums) {
int i,n;
n=nums.size();
vector<int> cnt(n,0);
//判断是否存在不在0~n-1这个范围的数
for(i=0;i<n;i++)
if(nums[i]<0||nums[i]>=n)
return -1;
//遍历的同时去统计每个数字出现的次数
for(i=0;i<n;i++){
cnt[nums[i]]++;
if(cnt[nums[i]]>1)
return nums[i];
}
return -1;
}
};
注意如果只用了一次循环,判断是否存在不在0~n-1范围的数,同时去统计各个数字出现的次数,如果有>=2的就返回——问题出现在,在出现不合法的数字前,就有可能找到重复出现2次的数字,那么会返回那个数字,而不是-1。因此——要先循环一遍判断是不是存在不合法的数字!
题解里面的
题解:修改数组。
数组大小为n,数字范围是0~n-1,实际上可能存在没有重复数字的情况,即每个数字出现一次。
题解思想是交换数组中的数字,使得nums[i]=i:
- 如果nums[i] != i,nums[i] = x,此时nums[x] = x,那么x就出现了不止一次,直接返回x;
- 如果nums[i] != i,nums[i] = x,且此时nums[x] != x,x应该在nums[x]这个位置上,故交换nums[i]和nums[x],交换以后不需要i++,直接重复上面的判断;
- 如果nums[i] = i,直接i++;
代码是去遍历数组,如果存在第一种情况就直接返回,结束;如果是第二种情况,就继续去交换(此时nums[i]已经变化,不能i++,去判断下一个数),那么是否存在死循环??——考虑最坏的情况,就是n个数字,没有重复的,且i为0的时候,就去一直重复情况2,直到后面的n-1个数字都到了正确的位置,之后就一路i++,结束了!时间复杂度依旧是O(n),但是没有增加额外的空间,代码如下:
class Solution {
public:
int duplicateInArray(vector<int>& nums) {
int i,n=nums.size();
int ans=-1;
int temp;
//判断是否存在不在0-n-1范围内的数
for(i=0;i<n;i++)
if(nums[i]<0||nums[i]>=n){
return -1;
}
//将每个数放在正确的位置上
for(i=0;i<n;){
if(nums[i]!=i&&nums[nums[i]]==nums[i]){
ans=nums[i];
break;
}
else if(nums[nums[i]]!=nums[i]){
temp=nums[nums[i]];
nums[nums[i]]=nums[i];
nums[i]=temp;
}
else i++;
}
return ans;
}
};
题库14.不修改数组找出重复的数字
题目链接14. 不修改数组找出重复的数字
题目内容如下:
此题与之前不同的地方在于——数组长度是n+1,数字范围是1~n,根据抽屉原理(n个抽屉放n+1件物品),至少存在一个数字不止出现一次! 需要注意的是,题目中并未像13题一样说明,数组中可能出现不在1~n范围的数字,一次默认数组内数字都是该范围内的。
此题依然可以直接遍历数组,统计数组中各数字出现的次数,但是需要O(n)的空间复杂度。
【借鉴】很有意思的题解是——利用二分,数字范围是1~n,将其分成[1 , n/2 ]和[ n/2 + 1 ,n ]两个区间,分别统计这两个数字区间内数字的数量,肯定有一个区间内的数字个数比数字区间(end-start+1)更大;那么再将这个区间二分,直到最后区间只剩下一个数字,这个数字就是答案!代码如下:
class Solution {
public:
int duplicateInArray(vector<int>& nums) {
int n =nums.size();
//be和end表示数字区间
int be=1,end=n-1;
//在循环外就给mid赋值,是因为数组[1,1],不会进入循环
int mid=(be+end)/2;
//统计数字区间的数字个数
int cnt;
int i;
while(be<end){
cnt=0;
mid=(be+end)/2;
//统计
for(i=0;i<n;i++)
if(nums[i] >= be && nums[i] <= mid)
cnt++;
//在前面区间
if(cnt > mid-be+1)
end = mid;
//在后面区间
else
be = mid + 1;
}
//最后be=end
return end;
}
};
需要注意的是,最后的数字区间一定是只有一个数,这个数字就是答案,此时be = end,退出循环,故返回be和end都ok,但是不可以返回mid!!!!因为最后可能是be = mid + 1然后be = end,此时mid !=be !=end,返回min是错误的!
😥😥😥😥 Acwing更像是考技巧??大家的代码都好精简,好高级!学习!