FindDuplicateNumber
- 问题描述:给定一个数组长度为N,里面元素的取值范围是1~N-1。所有会有一个数是重复的,请找到这个重复的数字并返回。
- 解法1:暴力
- 最直接的办法就是利用两重循环来寻找
- 当碰到相同的数字的时候就返回
- 时间复杂度是O(N)
- 解法2: 二分法
- 我们知道我们N个数是分布在1~N-1之间的。所有假设我们统计1~N/2, N/2-N-1之间的数字,如果哪一方的数字多余一半,则重复的数字就在其中。
- 通过这样的方式我们可以二分的查找多余的数字
- 时间复杂度是O(NlogN)
- 解法3:
- 我们尝试构造下面的数列:
-
X
0
=
n
u
m
s
[
0
]
X_0 = nums[0]
X0=nums[0]
-
X
1
=
n
u
m
s
[
X
0
]
X_1 = nums[X_0]
X1=nums[X0]
-
X
2
=
n
u
m
s
[
X
1
]
X_2 = nums[X_1]
X2=nums[X1]
- …
-
X
i
=
n
u
m
s
[
X
i
−
1
]
X_i = nums[X_{i-1}]
Xi=nums[Xi−1]
- 因为我们数字里存在重复的数字,所有一定存在一个
X
i
X_i
Xi在
[
X
0
,
X
i
−
1
]
[X_0,X_{i-1}]
[X0,Xi−1]之中出现过。形成如下所示的带环链表。
![cycle linked list](https://i-blog.csdnimg.cn/blog_migrate/5dfd9a732866ab55abbfc7ad41bcbcbd.png)
- 接下来我们就是要找到入口前的一个点,他就是我们重复的数字。
- 那么我们如何找呢?
- 我们先利用两个point,一快一慢,快的走两步,慢的走一步。
- 因为我们存在环,所有两个指针一定会相遇。
- 假设两个相遇在某一点,则此时再让fast从头开始,然后slow和fast都每次走一步。
- 两者相遇的时候就是我们的重复的数字
- why?
- 假设一开始两者相遇的时候slow的走了N步,则fast的走了2N步
- 接下来我们分析一下这N步是由什么构成的?
- 首先在进环前走了D步
- 从环的起点到相遇点走了K步。
- 整个环走了i圈,假设环的长度是C
- 则N = D + K + C*i
- 那么对于2N,我们同样有2N = D + K + C*j
- 我们消去N可以得到2D + 2K + 2Ci = D + K + Cj =>D+K = C(j-i). 注意j>i是成立的。
- 所有我们让一个point从头走D步。
- 那么从相遇的点相当于走D=C(j-i)-K。
- 相当于我们差K个step可以走满C(j-i+1)圈。
- 因为我们相遇的点距离环的入口点是K。所有此时一定是相遇在起点的。
- 上面我们分析了为什么可以相遇在起点。又因为我们两个point在经历起点前的一个点也一定是相同的,参照上图。
- 所以我们可以得到当第一次两个相同的时候,就是我们的重复数字。
- 如果说上面方法难以理解的话,我们可以在中加加上一步
- 找到相遇的点
- 计算环的长度L
- 初始化两个point
- 第一个point先走L步
- 然后两个point同时走,当两个point相遇的时候就是环的入口点。
- 三种方法的代码:
#include <vector>
#include <iostream>
using namespace std;
class Solution {
public:
int findDuplicate(vector<int>& nums) {
int size = nums.size();
for(int i=0;i<size;i++){
for(int j=i+1;j<size;j++)
if(nums[i] == nums[j])
return nums[i];
}
return -1;
}
int findDuplicateV2(vector<int>& nums){
int size = (int) nums.size();
if(size > 1){
int slow = nums[0];
int fast = nums[nums[0]];
while(slow != fast){
slow = nums[slow];
fast = nums[nums[fast]];
}
slow = 0;
while(slow != fast){
slow = nums[slow];
fast = nums[fast];
}
return slow;
}
return -1;
}
int findDuplicateV3(vector<int>& nums){
int size = (int) nums.size();
if(size > 1){
int leftCount = 0;
int rightCount = 0;
int left = 1;
int right = size - 1;
while(left < right){
int mid = (right + left) / 2;
int count = 0;
for(auto c: nums){
if(c <= mid){
count += 1;
}
}
if(count > mid){
right = mid;
}else{
left = mid + 1;
}
}
return left;
}
return -1;
}
static void solution(){
Solution solution1;
vector<int> nums = {1, 3, 4, 2, 2};
cout<<solution1.findDuplicateV3(nums)<<endl;
}
};