问题描述
给定一个包含 n + 1 个整数的数组 nums,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个重复的整数。假设只有一个重复的整数,找出这个重复的数。
说明:
- 不能更改原数组(假设数组是只读的)
- 只能使用额外的 O ( 1 ) O(1) O(1) 的空间
- 时间复杂度小于 O ( n 2 ) O(n^2) O(n2)
- 数组中只有一个重复的数字,但它可能不止重复出现一次。
解题报告4
快慢指针法
很明显是,我们需要构成一个环,然后找到环的起点,即为重复的数。
具体解释见Leetcode 142. 环形链表 II【快慢指针法求环的起点】。
二分法【抽屉原理】
因为数组中每个数的范围为: [ 0 , n ] [0,n] [0,n],我们取中间数 n / 2 n/2 n/2,如果整个数组中小于等于 n / 2 n/2 n/2 严格大于 n / 2 n/2 n/2,那么重复元素出现在 [ 0 , n / 2 ] [0,n/2] [0,n/2]这个区间内,否则在区间 [ n / 2 + 1 , n ] [n/2+1,n] [n/2+1,n]内。
实现代码
快慢指针法实现
class Solution{
public:
int findDuplicate(vector<int>nums) {
int slow = nums[0];
int fast = nums[nums[0]];
//寻找相遇点
while (slow != fast) {
slow = nums[slow];
fast = nums[nums[fast]];
}
//slow 从起点出发, fast 从相遇点出发, 一次一步
slow = 0;
while (slow != fast) {
slow = nums[slow];
fast = nums[fast];
}
return slow;
}
};
二分法实现
#include <iostream>
#include <vector>
using namespace std;
class Solution {
public:
int findDuplicate(vector<int> &nums) {
int len = nums.size();
int left = 1;
int right = len - 1;
while (left < right) {
int mid = left + (right - left) / 2;
int cnt = 0;
for (int num:nums) {
if (num <= mid) {
cnt++;
}
}
// 根据抽屉原理,小于等于 4 的数的个数如果严格大于 4 个,
// 此时重复元素一定出现在 [1, 4] 区间里
if (cnt > mid) {
// 重复的元素一定出现在 [left, mid] 区间里
right = mid;
} else {
// if 分析正确了以后,else 搜索的区间就是 if 的反面
// [mid + 1, right]
// 注意:此时需要调整中位数的取法为上取整
left = mid + 1;
}
}
return left;
}
};
//作者:liweiwei1419
//链接:https://leetcode-cn.com/problems/find-the-duplicate-number/solution/er-fen-fa-si-lu-ji-dai-ma-python-by-liweiwei1419/
//来源:力扣(LeetCode)
//著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
参考资料
[1] Leetcode 287.寻找重复数
[2] 题解区:windliang
[3] 题解区:liweiwei1419
[4] 快慢指着找环入口