最近刷到一道medium难度但方法很创新的题。题目简要叙述如下:
数组nums长度为n+1,1<=nums[i]<=n,且满足恰好有一个重复数字。要求不能修改原数组nums、时间复杂度小于O(n^2)、空间复杂度O(1)找出这个重复数字。
乍一看题目要求很高,实际上凭空想根本就想不出来。看了Leetcode给的答案才发现题目要求是为答案量身定制的。思路如下:
由于nums.length == n + 1, 1 <= nums[i] <= n,故nums[nums[i]]有定义。
若令i := 0
重复:生成节点node[i],节点的值为nums[i],节点的next值为nums[nums[i]],
i := nums[i]
结束
则可生成有循环的链表如下图。必有2个节点的next值指向同一个节点,这个节点的值即为重复的数字。
运行Floyd的龟兔赛跑算法:指针hare和tortoise初始指向链表头节点,hare每次前进两步,tortoise每次前进一步,则兔子必在环内追上乌龟。追上时兔子走的距离是乌龟的2倍,即
(F + a) * 2 = F + a + n * C
其中F是入环之前的距离,a是环内走的距离,C是环的步数,n是环数。
即
F + a = n * C
兔子追上乌龟后,兔子再从链表头出发,乌龟从追上的位置原地出发,每次均向前移动一步,则经过F步后,兔子走了F步到了入环节点,乌龟在环内共走a + F = n * C步,也是入环节点,此时他们相遇作为判断条件。入环节点的值就是重复数字。
public int findDuplicate(int[] nums) {
int tortoise = nums[0], hare = nums[nums[0]];
while (tortoise != hare) {
tortoise = nums[tortoise];
hare = nums[nums[hare]];
}
tortoise = 0;
while (tortoise != hare) {
tortoise = nums[tortoise];
hare = nums[hare];
}
return hare;
}