方法一:应用鸽笼原理进行二分法查找
因为在1~n的范围内最多只能放n个不同的数字,但现在数组放了n+1个数字,所以至少有一个重复。
对于在1~n范围内的某个数字m,那么数组中小于等于m的数字最少有m个,并且刚好为m的时候,1~m之间不会有重复,比m大时,重复数字必在1~m中。
public class Solution {
public int findDuplicate(int[] nums) {
int low = 1, high = nums.length-1;
while (low<high) {
int mid = (low+high)/2;
int count = 0;
for(int num: nums) if (num<=mid) count++;
if (count > mid) high = mid; else low = mid + 1;
}
return low;
}
}
方法二:映射找环法
假设数组中没有重复,那我们可以做到这么一点,就是将数组的下标和1到n每一个数一对一的映射起来。比如数组是213
,则映射关系为0->2, 1->1, 2->3
。假设这个一对一映射关系是一个函数f(n),其中n是下标,f(n)是映射到的数。如果我们从下标为0出发,根据这个函数计算出一个值,以这个值为新的下标,再用这个函数计算,以此类推,直到下标超界。实际上可以产生一个类似链表一样的序列。比如在这个例子中有两个下标的序列,0->2->3
。
但如果有重复的话,这中间就会产生多对一的映射,比如数组2131
,则映射关系为0->2, {1,3}->1, 2->3
。这样,我们推演的序列就一定会有环路了,这里下标的序列是0->2->3->1->1->1->1->...
,而环的起点就是重复的数(Floyd's Algorithm)。
考虑如下的一个序列:
从idx=0开始,对应的val是下一个idx,建立一个类似链表的结构, 1->3->2->4->2 将会出现一个环,之后就是链表判环的解法,可以用快慢指针来做。环的入口就是重复的数字。
class Solution {
public int findDuplicate(int[] nums) {
int slow = nums[0], fast = nums[0];
for(;;) {
slow = nums[slow];
fast = nums[nums[fast]];
if(slow==fast)
break;
}
fast = nums[0];
while (slow != fast) {
slow = nums[slow];
fast = nums[fast];
}
return slow;
}
}