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(n^2)$.
There is only one duplicate number in the array, but it could be repeated more than once.
这道题在没有限制的情况下做法有很多种,例如排序,然后找到 a[i] == a[i + 1]的,或者,桶排序,记录一下每个数出现了几次。
但是题目增加了很多限制条件
- 不能更改原数组(假设数组是只读的)
- 只能使用额外的 $O(1)$ 的空间
- 时间复杂度小于 $O(n^2)$
- 数组中只有一个重复的数字,但它可能不止重复出现一次
一开始想似乎是没有思路,但是如果把题目稍作变换,马上就可以变成很熟悉的题型。
数组长度是 n + 1,这意味着数组下标是 0 ~ n,同时,数组中的数的范围是 1 ~ n,因此,数组中元素的值的取值范围是数组下标取值范围的子集。
那么,如果把数组想象成一个不存放内容、只有指针的链表,数组元素的值是当前下标的后一个链表结点下标。举例来说,a[1] = 3,那么可以认为,标号为 1 的结点的 next 是编号为 3 的结点,即 node1 -> next = node3,类似地,如果 a[2] = 4, a[4] = 5,那么这个链表就是 node2 -> node4 -> node5。
这样一来,问题就换成了,给定一个链表,链表中有一个环,问,环的起点在哪?
典型的双指针问题。
至于,为什么链表一定有环,这是由题意显然的,那么,为什么所求的答案就是环与直线的交点?
简单说,就是:快指针走过 a + b + c + b,慢指针走过 a + b,又 2(a + b) = a + b + c + b,所以c = a。
详细的证明可以参考:https://blog.csdn.net/u013077144/article/details/81070415,以及https://blog.csdn.net/monkeyduck/article/details/50439840
代码如下:
class Solution {
public int findDuplicate(int[] nums) {
int slow = nums[0];
int fast = nums[nums[0]];
while (slow != fast) {
slow = nums[slow];
fast = nums[nums[fast]];
}
fast = 0;
while (slow != fast) {
slow = nums[slow];
fast = nums[fast];
}
return slow;
}
}
这里的
while (slow != fast) {
slow = nums[slow];
fast = nums[nums[fast]];
}
就相当于链表写法中的
while (fast->next != nullptr && fast->next->next != nullptr) {
fast = fast->next->next;
slow = slow->next;
if (fast == slow) break;
}

为什么”链表一定有环,这是由题意显然的”?
@Hi要将 1 ~ n 填入 n + 1 长度的数组,由鸽笼原理,必存在至少出现 2 次的数(若每个数最多出现一次,则只能填满长度为 n 的数组)。根据上文的描述,将数组各位置的元素值看作链表的 next 指针指向的位置,那么,那个至少出现了 2 次的数,就会有多个入口(即多个数指向他),于是环存在。
666