287. Find the Duplicate Number
题目
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:
1. You must not modify the array (assume the array is read only).
2. You must use only constant, O(1) extra space.
3. Your runtime complexity should be less than O(n2).
4. There is only one duplicate number in the array, but it could be repeated more than once.
思路
在做这题之前,先做了一系列的无额外空间的数组题,例如:41. First Missing Positive
这些数组题的核心是交换数组中的数字,使得数字放在对应的位置,比如数字2在交换之后,会被放在第二位,也就是下标为1的地方。遍历整个数组,假设当前遍历下标为1,那么使得:nums[i] == nums[nums[i]-1],就可以保证数字在对应位上,或者遇到了重复的数字。之后就相当于利用题目条件,将无序的数组变得相对有序,然后就容易解了。
于是看到287这题的时候,第一反应就是类似的思想,交换数字!但发现题目要求不能修改数组,那么就想到了双指针。最初的想法(错误的)是固定住一个指针i,另一个指针按照数字nums[i]跳到nums[nums[i]-1]的地方,直到两个指针的数相等,就得到结果;或者指针不再变化时,就移动固定的指针。这种解法能解决部分情况,但遇到一个死循环但都不等于固定指针的时候,就GG了。
于是想到,跳来跳去的指针最终都会陷入死循环,那么死循环中必定有一个数是重复的。先证明一定存在死循环:
题目说有1~n共n个不同的数,但数组长度是n+1,于是一定有一个数重复。我们按照nums[nums[i]]来跳指针(为什么不用nums[nums[i]-1]?),那么一定会出现死循环,因为没有一次跳指针能跳出数组范围。
证明一定存在死循环之后,题目就变成了类似在单向链表中寻找回环入口,只需要证明这里的死循环入口下标就是重复的那个数。证明如下:
根据题意,只有一个数是重复的(数量可能为多个),而循环的入口就是第一个访问两次的数,按照我们的跳指针方法nums[nums[i]]
,只有可能是当i!=j && nums[i]==nums[j]
时,有nums[nums[i]] == nums[nums[j]]
。那么这个nums[i]就是我们要找的重复的数了。进一步可以得知,当发现nums[i] == nums[j]时,就可以判断这个值是重复的数,不需要再跳一步找下标是否相等了。
于是我们可以用双指针找回环的算法来解决这题,至于为什么这个算法是正确的,这里有个大大写了一篇很好的博文:http://blog.csdn.net/l294265421/article/details/50478818,而且287这题也有一个很好的解读:http://keithschwarz.com/interesting/code/?dir=find-duplicate。
class Solution {
public:
int findDuplicate(vector<int>& nums) {
if(nums.size() < 2) {
return -1;
}
int slow = nums[nums[0]];
int fast = nums[slow];
// cout << slow << ", " << fast << endl;
while(slow != fast) {
slow = nums[slow];
fast = nums[fast];
fast = nums[fast];
// cout << slow << ", " << fast << endl;
}
fast = nums[0];
while(slow != fast) {
slow = nums[slow];
fast = nums[fast];
}
return slow;
}
};