法一:抽屉法,放回原位,在放的过程中,如果自己不在原位,但是原位已经被自己的复制品霸占了,就直接返回当前数字,当前数字就是重复的,否则就交换,把当前数字放回原位。缺点就是要swap,会改变原数组.
法二:快慢指针,只要是有重复数字就一定存在环。这个环和链表的环差不多,比如31342这个数组,我们从0开始走,走到当前数组的某一位,就把那一位的值作为下一步要走的下标。0->3->4->2->3->4->2,这就进入了环。但是这里的快慢指针怎么走呢,如何体现快慢的思想,我们把快慢指针一开始都设为0,慢指针走的就是nums[slow],快指针走的就是nums[nums[fast]],这样就可以体现快慢的思想了,因为一定会有环,所以将它俩就初始化成这个,当相等时退出循环。
为了找出环的起点,就将slow重置为整个链表的起点0,然后快慢指针都是一步一步走,相等的地方就是环的起点,详情请见循环链表II
class Solution {
public:
int findDuplicate(vector<int>& nums) {
//抽屉法,放回原位,在放的过程中,如果自己不在原位,且原位已经被自己的复制品霸占了就返回当前数字
//1放在0号位,放在比自己小1的位置上
// for(int i = 0; i < nums.size(); ++i){
// while(nums[i] != i+1){
// if(nums[nums[i]-1] == nums[i]) return nums[i];
// swap(nums[i],nums[nums[i]-1]);
// }
// }
// return -1;
//如何不修改数组,就是不能swap
int slow = 0;
int fast = 0;
slow = nums[slow];
fast = nums[nums[fast]];
//因为一定有重复,所以一定有环
while(slow != fast){
slow = nums[slow];
fast = nums[nums[fast]];
}
slow = 0;
while(slow != fast){
slow = nums[slow];
fast = nums[fast];
}
return fast;
}
};
法一:抽屉
法二:快慢指针,起点是0,不是nums[0]!!
法三:变种二分,每次找一个mid,然后遍历,维护一个cnt来记录小于等于mid的数,然后根据cnt来判断向左还是向右。
mid = (1 + 5) / 2 = 3 arr小于等于的3有4个(1,2,2,3),1到3中肯定有重复的值
mid = (1 + 3) / 2 = 2 arr小于等于的2有3个(1,2,2),1到2中肯定有重复的值
mid = (1 + 2) / 2 = 1 arr小于等于的1有1个(1),2到2中肯定有重复的值
class Solution {
public:
int findDuplicate(vector<int>& nums) {
// int slow = 0, fast = 0;
// while(1){
// slow = nums[slow];
// fast = nums[nums[fast]];
// if(slow == fast) break;//要在内部判断,因为slow和fast一开始设为起点是一样的
// //一般快慢指针都是在里面判断的,因为起点都是一样的
// }
// slow = 0;
// while(slow != fast){
// slow = nums[slow];
// fast = nums[fast];
// }
// return slow;
//二分,每次找一个mid,然后遍历,维护一个cnt来记录小于等于mid的数,然后根据cnt来判断向左还是向右
int l = 0, r = nums.size()-1;
//不是具体找某个数,相等的时候退出
while(l < r){
//mid = (1 + 5) / 2 = 3 arr小于等于的3有4个(1,2,2,3),1到3中肯定有重复的值
//mid = (1 + 3) / 2 = 2 arr小于等于的2有3个(1,2,2),1到2中肯定有重复的值
//mid = (1 + 2) / 2 = 1 arr小于等于的1有1个(1),2到2中肯定有重复的值
int mid = l + (r-l)/2;
int cnt = 0;
for(int x : nums){
if(x <= mid) cnt++;
}
if(cnt <= mid) l=mid+1;
else r=mid;
}
return l;
}
};