LeetCode 287. 寻找重复数
原题地址:
https://leetcode-cn.com/problems/find-the-duplicate-number/comments/
题目不难但是挺巧妙的,而且解法很多,可以学习一下。
在 n + 1 个元素的数组里,放[1 , n]之间的元素,由于抽屉原理(又见抽屉原理)那么我们知道,一定会有至少一个重复元素,而题目规定是刚好一个重复元素,所以找到这个重复元素。
ps : 注意限制条件,难点就在限制条件。
- 解法一(判环):
我们把数组记录的数看成对应的数组索引,那么如果是n个不同的数,那就是一根长度为n的链表,但是有重复的数所以一定会出现环,那么我们用快慢指针判环。
快慢指针中,因为每一次移动后,快指针都会比慢指针多走一个节点,所以他们之间在进入环状链表后,不论相隔多少个节点,慢指针总会被快指针赶上并且重合,以重合的点从两边往中间找,找到那个点就是那个进入环的点,即数组中重复的那个数。
具体的可以自己画图理解,对快慢指针有兴趣可以百度。
参考代码一:
class Solution {
public:
int findDuplicate(vector<int>& nums) {
int slow = 0 , fast = 0;
while (true){
slow = nums[slow];
fast = nums[nums[fast]];
if(slow == fast) break;
}
while (true){
slow = nums[slow];
fast = nums[fast];
if(slow == fast) return nums[slow];
}
}
};
- 解法二(位运算)
位运算解法,在LeetCode我看用这个解法的比较少,但我觉得这个解法最好想。1—n 的数二进制的每一位 1 的个数应该是确定的,找到每一位多出的 1 就能组成重复的那个数。
参考代码二:
class Solution {
public:
int findDuplicate(vector<int>& nums) {
int n = nums.size(); // 这个n实际上是n + 1;
int ans = 0;
for(int i = 0 ; i < 32 ; i++){
int k = 1 << i;
int cnt1 = 0, cnt2 = 0;
for(int j = 0 ; j < n ; j++){
if(k & j) cnt1++; // 这里算了0 —— n但0不影响;
if(k & nums[j]) cnt2++;
}
if(cnt2 > cnt1) ans += k;
}
return ans;
}
};
- 解法三(二分)
在1 — n 之间二分,我们可以知道如果没有重复的数那么数组中小于mid的数的个数一定等于mid,如果大于mid,那么我们就可以知道重复的数一定在[l , mid]这个范围内,所以二分找到这个数就好了。
参考代码三:
class Solution {
public:
bool check(int m,vector<int>& nums){
int cnt = 0;
for(int i = 0 ; i < nums.size() ; i++){
if(nums[i] <= m) cnt++;
}
return cnt > m;
}
int findDuplicate(vector<int>& nums) {
int l = 1 , r = nums.size() - 1;
int ans = 0;
while (l <= r){
int mid = (l + r) >> 1;
if(check(mid,nums)){
ans = mid;
r = mid - 1;
} else l = mid + 1;
}
return ans;
}
};
看似简单的题有时候也能带来很多思维启发,值得注意。