Note:和《剑指offer》面试题3:数组中的重复数字 类似
解法一:映射找环法O(n)
我当时想到的解法,要么时间复杂度不合要求,要么空间复杂度不合要求,看了这道题的discussion区,结合两个回答,总算是明白了这道题该怎么解比较简单。
两个回答的链接:
- Java O(n) time and O(1) space solution. Similar to find loop in linkedlist.
- My easy understood solution with O(n) time and O(1) space without modifying the array. With clear explanation.
题目:Given an array nums containing n + 1 integers where each integer is between 1 and n (inclusive), prove that at least one duplicate number must exist. Assume that there is only one duplicate number, find the duplicate one.
Note:
- You must not modify the array (assume the array is - read only).
- You must use only constant, O(1) extra space.
- Your runtime complexity should be less than O(n2).
- There is only one duplicate number in the array, but it could be repeated more than once.
代码如下:
public int findDuplicate(int[] nums) {
if (nums.length > 1){
int slow = nums[0];
int fast = nums[nums[0]];
//以下为求两个指针第一次相遇的点
while (slow != fast){
slow = nums[slow];
fast = nums[nums[fast]];
}
fast = 0;
//以下求重复元素的节点,即重复元素的入口节点
while (fast != slow){
fast = nums[fast];
slow = nums[slow];
}
return slow;
}
return -1;
}
思路
例如下面这个数组:
index: 0 1 2 3 4 5
value: 1 2 3 2 1 4
因为数组值的特殊性,所以配合其索引index,可以形成index的图,又因为有重复的数字存在,所以图的某个部分会形成一个circle,circle的入口为重复的那个数。
该数组索引形成的图为:
第一部分,如果安排一个slow的index(每次一步)和fast的index(每次两步),它们走着最终会在circle中一直循环出不去,但迟早会相遇。类似数学题中的,甲乙以不同速度同向出发,最终快的会赶上慢的。然而,相遇停下的地方不一定是circle入口。
第二部分,一个index从0出发,一个从停下的位置出发,步长都为1。假设circle之前的直线长度为m(不包括circle入口),circle长度为n,相遇点在circle的第k处(circle入口为第1处),则fast走过的路程为m + i*n + k,slow走过的路程为m+j*n+k,二者所花时间相等,速度已知。可得,等式(m + i*n + k)/2 = m+j*n+k ==> m = (i-2j)*n - k;(i-2j)不为0,令s = i - 2j,s为正整数,则m = sn - k , m = (s-1)n + (n - k)。slow正在k点,这个特殊关系使得如果将fast放回0点,当fast到达circle的入口时走了m步,而slow走了(n - k)又绕了(s-1)n圈回到了circle的入口处,它们的相遇点正好是入口。
解法二:二分法O(n*lgn)
利用鸽巢原理的二分解法
上面这个链接解释的很清楚了,一看就懂。