1 数字范围长度与数组长度相同
最简单的解法就是遍历数组并用一个哈希表存起来,当再次遍历到同一个数字的时候就找到了重复的元素。
class Solution {
public:
int findRepeatNumber(vector<int>& nums) {
unordered_set<int> s;
for(auto &n : nums){
if(s.find(n) != s.end()) return n;
else s.insert(n);
}
return -1;
}
};
这种解法时间复杂度和空间复杂度都是线性阶(要用一个额外的哈希表来存元素)。
还有一个时间复杂度线性阶,空间复杂度常数阶的方法。
还是遍历原数组,因为数组中元素范围和元素个数是一致的,我们可以让某索引处的值等于索引,比如索引为1的地方数组值为1,索引为2的地方数组值为2,索引为i的地方数组值为i。
基本做法是:
当我们遍历到第i个位置,其元素为nums[i],如果 i == nums[i],则遍历下一个位置。
如果i != nums[i],则将数组索引i处的元素与索引为nums[i]处的元素互换(在互换操作之前,先判断nums[i]为索引处的数组元素值是否是nums[i],如果是的话,那么索引i处和索引nums[i]处的值都是nums[i],相当于就找到了重复的数字,返回nums[i]即可)。
然后再判断i与nums[i]是否相等,如果相等就遍历下一个,如果不相等就继续将i处的元素与nums[i]处的元素互换。
class Solution {
public:
int findRepeatNumber(vector<int>& nums) {
int len = nums.size();
for(int i = 0; i < len;){
if(nums[i] == i) ++i;
else if(nums[nums[i]] == nums[i]) return nums[i];
else swap(nums[i], nums[nums[i]]); // 当前位置上元素不等于当前索引,将当前位置上元素和以当前位置为索引的元素交换
}
return -1;
}
};
2 数字范围比数组长度小1
比如数组长度 n + 1,数字范围1~n。
要求不能修改原数组。
用二分的思想,二分法不仅局限于数组上查找元素,只要牵涉到有序序列都可以用二分法。之所以经常在数组上用二分法查找元素也是因为数组索引是天然有序的序列。
这里我们也可以提取出一个有序序列,其数字范围1~n。我们从数字1~n的中间值mid,将这个序列分为左右两部分,如果数组中元素在左边数字范围的元素个数大于数字范围,那么说明左边一定有重复值,否则说明右边一定有重复值,这样不断每次将搜索区间缩小一半,最终区间长度为1,而这个区间中的数组元素个数大于1,那当然这个数就是重复的数字。
#include<bits/stdc++.h>
using namespace std;
int countRange(vector<int> &nums, int start, int end){
int cnt = 0;
int len = nums.size();
for(int i = 0; i < len; ++i){
if(nums[i] >= start && nums[i] <= end) ++cnt;
}
return cnt;
}
int getDuplication(vector<int> &nums){
int start = 1, end = nums.size() - 1;
while(end >= start){
int mid = ((end - start) >> 1) + start;
int cnt = countRange(nums, start, mid);
if(end == start){ // 区间只剩下一个数,如果它出现的次数大于1,当然是重复的数
if(cnt > 1) return start;
// else break;
}
if(cnt > (mid - start + 1)){ // 如果一个区间中的数大于区间长度,那么这个区间中有重复的数
end = mid;
}else{
start = mid + 1;
}
}
return -1;
}
int main(){
vector<int> v{1,2,2,2,3,3,3,4,6}; // 10个数
int ans = getDuplication(v);
cout<<ans<<endl;
return 0;
}