做网站图标的软件视频网站开发工具
龟兔赛跑算法(Floyd’s Cycle-Finding Algorithm)寻找重复数
问题描述
给定一个长度为 N+1 的数组 nums,其中每个元素的值都在 [1, N] 范围内。根据鸽巢原理,至少有一个数字是重复的。请找出这个重复的数字。
要求:
- 时间复杂度
O(N) - 空间复杂度
O(1)(不能使用哈希表等额外存储)
算法思路(龟兔赛跑法)
我们可以将数组视为一个链表,其中 nums[i] 表示 i → nums[i] 的边。由于存在重复数字,这个链表必然存在一个环,而环的入口就是重复的数字。
步骤:
-
快慢指针找相遇点(判断是否有环):
- 慢指针
slow每次走1步:slow = nums[slow] - 快指针
fast每次走2步:fast = nums[nums[fast]] - 直到
slow == fast,说明两者在环内相遇。
- 慢指针
-
找环的入口(即重复的数字):
- 将
fast重置到起点0。 slow和fast都每次走1步,直到再次相遇,相遇点就是重复的数字。
- 将
代码实现
public int findDuplicate(int[] nums) {int slow = 0;int fast = 0;// 第一阶段:快慢指针找相遇点do {slow = nums[slow]; // 慢指针走 1 步fast = nums[nums[fast]]; // 快指针走 2 步} while (slow != fast);// 第二阶段:找环的入口(重复数字)fast = 0;while (slow != fast) {slow = nums[slow];fast = nums[fast];}return slow; // 或 fast,此时它们相等
}
为什么这个算法有效?
-
第一阶段(找相遇点):
- 假设环的长度为
L,环外长度为F。 - 当
slow进入环时,fast已经在环内,且距离slow为d(0 ≤ d < L)。 - 由于
fast每次比slow多走1步,它们会在L - d步后相遇。
- 假设环的长度为
-
第二阶段(找环入口):
- 设
slow和fast在环内相遇时,slow走了F + a步(a是环内走的步数)。 fast走了F + a + kL步(k是整数,因为fast可能绕环多圈)。- 由于
fast速度是slow的2倍:
[
2(F + a) = F + a + kL \implies F + a = kL \implies F = kL - a
] - 这意味着,从起点走
F步,刚好到达环的入口(即重复数字)。
- 设
示例
输入: nums = [1, 3, 4, 2, 2]
步骤:
- 第一阶段:
slow = 0 → 1 → 3 → 2 → 4 → 2 → 4 → ...fast = 0 → 1 → 3 → 2 → 4 → 2 → 4 → ...- 它们在
2或4相遇(具体取决于实现)。
- 第二阶段:
fast重置到0,然后slow和fast都每次走1步:fast = 0 → 1 → 3 → 2slow = 4 → 2
- 它们在
2相遇,因此2是重复数字。
复杂度分析
- 时间复杂度:
O(N)(最多遍历2N次)。 - 空间复杂度:
O(1)(仅用两个指针)。
总结
龟兔赛跑算法是一种高效的链表环检测方法,适用于:
- 检测链表是否有环。
- 找出数组中的重复数字(数组值在
[1, N]范围内)。 - 不修改原数组,且满足
O(1)额外空间。
