1、问题描述
给定一个包含n+1个整数的数组nums,其中,每个整数的取值范围为[1,n],根据鸽巢原理可知,至少存在一个重复的整数。假设只有一个重复的数,找出这个数。
2、解题思路
- 边界条件与特殊情况:(1)nums中整数的个数小于2;
- 问题分析:以nums=[1,3,4,2,3]为例,索引index的范围是在[0,n],元素值value的范围是[1,n],想象一下,把数组看成一个链表,数组中每一个位置上的元素值作为下一个元素的索引,当有元素重复时,会得到下面这样的带有环的索引链表:
index | value |
---|---|
0 | 1 |
1 | 3 |
2 | 4 |
3 | 2 |
4 | 3 |
- 思路1:根据上面的分析,类比判断链表中是否存在环问题的解题思路,采用快慢指针来解决这个问题,整个思路分为两个步骤:
- (1)检测环。具体地,快、慢指针fast、slow都从0开始,进行如下循环:slow每次进行一跳,而fast进行两跳,比如,一开始slow=i、fast=i,下一步中,slow=nums[i],而fast=nums[nums[i]]。当slow等于fast,循环停止,此时slow和fast都停在环上某个节点。
- (2)找到环的入口。具体地,finder指针从0开始,slow从上一步中slow和fast在环中的相遇的开始,进行如下循环:slow和finder指针每次都进行一跳。当finder等于fast时,循环停止,此时finder指向的为环的入口。
- 这里可能有点难理解的是:为什么finder和slow会在环的入口处相遇?我们可以证明一下:
- 假设起点到环的入口处长度为m,环的周长为c,在检测环的过程中,快慢指针在环中相遇,此时,fast走了2n步,slow走了n步,根据以上假设可得出以下结论:
- 由于两个指针在环中相遇,所以多走的n步一定是c的整数倍,即n%c=0;
- fast和slow相遇时,slow在环中行走的距离是n-m,此时,若finder从起点开始,slow从相遇点开始,两者同步前进,每次一步,当finder走了m步到达环的入口处时,slow走了n-m+m=n步,而n又是c的整数倍,故两者会在环的入口相遇。
- 先声明一下数据结构:
- slow慢指针、fast快指针,finder指针。
- 初始化,一开始slow和fast都为0,finder也为0
- 处理逻辑: 检测环和找到环的入口。
- 这个方法的时间复杂度为,空间复杂度为。
- 思路2:一说到查找,且时间复杂度小于,则可以尝试二分查找,二分查找的思路如下:
- 由于数组的元素的取值范围在[1,n],所以需要查找的数的区间也left=1、right=n,取该区间的中位数mid,统计数组中小于等于mid的元素个数count,如果count<=mid,则说明重复的整数在[mid+1,right]区间,否则在[left,mid]之间,如果left <right,则继续在子区间查找。
- 由于进行了次查找,统计小于mid的元素个数count时间复杂度为,所以时间复杂度为,空间复杂度为。
3、代码实现
快慢指针实现:
class Solution {
public:
int findDuplicate(vector<int>& nums) {
if (nums.size() < 2){
return 0;
}
int slow=0;
int fast = 0;
int finder = 0;
while(true){
slow = nums[slow];
fast = nums[nums[fast]];
if(slow == fast){
break;
}
}
while(true){
slow = nums[slow];
finder = nums[finder];
if(slow == finder){
break;
}
}
return finder;
}
};
二分搜索实现:
class Solution {
public:
int findDuplicate(vector<int>& nums) {
if (nums.size() < 2){
return 0;
}
int left = 1;
int right = nums.size() - 1;
while(left < right){
int mid = (left + right) /2;
int count = countNums(nums,mid);
if(count <= mid){
left = mid + 1;
}
else{
right = mid;
}
}
return left;
}
int countNums(vector<int>& nums,int e){
int count = 0;
for(int i=0;i<nums.size();i++){
if(nums[i] <= e){
count ++;
}
}
return count;
}
};