题目大意:数组大小n+1,数组内元素范围[1,n],求出重复的元素
分析:面试中一般采用前几种做法。快慢指针的做法虽然很好,但是一般想不出来,除非你之前做过这道题。
方法一:排序后找相邻元素重复的即可。时间复杂度O(nlogn)+空间复杂度O(1)。
方法二:哈希表判重。时间复杂度O(n)+空间复杂度O(n)。
方法三:和leetcode442类似。
方法四:二分查找。时间复杂度O(nlogn)+空间复杂度O(1)。二分查找O(logn),循环统计O(n)。抽屉原理,5个抽屉放6个苹果,一定有一个抽屉放了两个苹果。元素从1~n,二分法猜哪个数重复,取中位数,遍历数组统计≤中间数的元素个数,如果个数大于中间数,就说明[left,mid]中有重复元素。比如14522367,先猜4是重复数字,发现有五个≤4的元素,说明重复元素肯定在[1,4]之间。
方法五:快慢指针Floyd算法。时间复杂度O(n)+空间复杂度O(1)。由题意可知,对于数值中的每个元素,都有它能指向的有效索引,因为元素值在1~n之间,索引下标在0~n之间。用类似判断循环链表的快慢指针,第一阶段找到相交点(相交点一定在环上,但不一定时入环点),第二阶段找到进入循环的点。慢指针走一步,快指针走两步。走到元素值指向的索引下标,比如指针当前在0,nums[0]=4,所以指针走到4。Floyd算法证明参考leetcode142(寻找链表入环节点)。
代码:
方法三:
class Solution {
public:
int findDuplicate(vector<int> &nums) {
int ans;
for(int i = 0;i < nums.size();i++){
if(nums[abs(nums[i]) - 1] > 0){
nums[abs(nums[i]) - 1] *= -1;
}
else ans = abs(nums[i]);
}
return ans;
}
};
方法四:
class Solution {
public:
int findDuplicate(vector<int> &nums) {
int left = 0,right = nums.size() - 1;
while (left < right) {
int mid = (left + right) / 2;
int cnt = 0; //统计小于等于mid的个数
for (int num:nums) {
if (num <= mid) {
cnt++;
}
}
if (cnt > mid) right = mid;
else left = mid + 1;
}
return left;
}
};
方法五:
class Solution {
public:
int findDuplicate(vector<int>& nums) {
//第一阶段:找到索引的intersection point(快指针追上了慢指针,代表有环)
int fast = nums[nums[0]],slow = nums[0];
while(slow != fast){
fast = nums[nums[fast]];
slow = nums[slow];
}
//第二阶段:找到入环点
int p = 0,q = slow;
while(p != q){
p = nums[p];
q = nums[q];
}
return p;
}
};