找出数组中重复的数字
题目来源https://www.acwing.com/problem/content/description/14/
思路:最直接的想法就是用map记录一下或者用set每次插入一个数判断一下长度有没有变化就好了。
class Solution {
public:
int duplicateInArray(vector<int>& nums) {
map<int,int>vis;
for(int i=0;i<nums.size();i++){
if(nums[i]<0||nums[i]>=nums.size())return -1;
vis[nums[i]]++;
}
for(int i=0;i<nums.size();i++){
if(vis[nums[i]]>=2){
return nums[i];
}
}
return -1;
}
};
但是map牺牲了大量空间换取时间,而set在空间和时间都不是最优的。
有更好的使用 O(1) 的额外空间的做法,由于数保证在0-n-1之内,则按如果没有出现重复,按升序排序每个数都可以和其数组下标对应。
利用这一点,我们可以将每个数和数组下标为这个数的数值进行交换,每次都可以至少将一个数放到正确的位置上,即使得num[i]=i;
如果有某个数和以其数为下标的数数值相同,且这个数的当前位置和该数值不对等,即说明出现了重复,在上一步无法进行交换。
class Solution {
public:
int duplicateInArray(vector<int>& nums) {
for(int i=0;i<nums.size();i++){
if(nums[i]<0||nums[i]>=nums.size())return -1;
}
for(int i=0;i<nums.size();i++){///swap最多n-1次,所以是o(n)
while(nums[i]!=nums[nums[i]]){///每次都会使一个数放到合适位置,直到当前位置的数等于其下标为此数的数,此时如果该数和该位置不同则说明出现重复
swap(nums[i],nums[nums[i]]);
}
if(nums[i]!=i){
return nums[i];
}
}
return -1;
}
};
如果题目要求不能修改数组呢......
根据鸽巢原理,n+1只鸽子放到n个笼子里,至少有一个笼子有2只鸽子。
应用到这道题的话,由n个数的数据范围在为0-n-1,分治的思想可以把这个数据区间(取值范围)分为两段,如果该序列出现重复元素的话,一定会在其中一个区间出现数的个数大于该“数据区间”的长度,依次二分下去,最后得到的一个数就是重复的数字。
如果序列没有出现重复元素,那么在第一轮比较的时候就有左右数据区间的个数相同,即刚好占满n个元素。时间复杂度o(nlogn)
class Solution { public: int duplicateInArray(vector<int>& nums) { for(int i=0;i<nums.size();i++){ if(nums[i]<0||nums[i]>=nums.size())return -1; } int l=0,r=nums.size()-1; while(l<r){ int mid=(l+r)>>1;/// l mid mid+1 r int s=0; for(auto x:nums){ s+= x>=l&&x<=mid;///在左区间的数 } if(s>mid-l+1)r=mid; else if(s==nums.size()-s)return -1; else l=mid+1; } return r; } };