讲解一下这段代码:class Solution { public int findDuplicate(int[] nums) { int slow = 0; int fast = 0; slow = nums[slow]; fast = nums[nums[fast]]; while(slow != fast){ slow = nums[slow]; fast = nums[nums[fast]]; } int pre1 = 0; int pre2 = slow; while(pre1 != pre2){ pre1 = nums[pre1]; pre2 = nums[pre2]; } return pre1; } } 作者:Kirsche 链接:https://leetcode.cn/problems/find-the-duplicate-number/solutions/58841/287xun-zhao-zhong-fu-shu-by-kirsche/ 来源:力扣(LeetCode) 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
时间: 2025-08-06 08:18:03 浏览: 6
<think>我们正在讨论LeetCode287题:寻找重复数。问题描述:给定一个包含n+1个整数的数组nums,其数字都在1到n之间(包括1和n),假设只有一个重复的整数,找出这个重复的数。注意:不能修改原数组(假设数组只读),只能使用额外的O(1)空间,时间复杂度小于O(n^2)。我们使用快慢指针(Floyd判圈算法)来解决这个问题。该算法分为两个阶段:第一阶段,用快慢指针找到相遇点;第二阶段,用两个慢指针(一个从头开始,一个从相遇点开始)找到环的入口,即重复的数。核心逻辑:将数组视为链表,数组的下标代表当前节点的地址,而数组的值代表指向下一个节点的地址。因为数组中至少有两个位置指向同一个节点(重复的数),所以必然存在环,而重复的数就是环的入口点。Java代码实现(参考示例):```publicclassSolution{publicintfindDuplicate(int[]nums){//第一阶段:找到快慢指针相遇点intslow=nums[0];intfast=nums[0];//初始先移动一次,然后使用循环do{slow=nums[slow];//慢指针每次走一步fast=nums[nums[fast]];//快指针每次走两步}while(slow!=fast);//第二阶段:找到环的入口intptr1=nums[0];//指针1从头节点开始intptr2=slow;//指针2从相遇点开始while(ptr1!=ptr2){ptr1=nums[ptr1];ptr2=nums[ptr2];}returnptr1;//或者ptr2,此时他们相等,即为重复的数}}```###代码逻辑分步详解1.**初始化快慢指针**:初始时,我们设置慢指针`slow`和快指针`fast`都指向数组的第一个元素(即`nums[0]`)。2.**第一阶段:寻找相遇点**-慢指针每次移动一步:`slow=nums[slow]`-快指针每次移动两步:`fast=nums[nums[fast]]`(相当于一次跳两步)-当快慢指针相遇时,说明存在环,此时停止循环。-注意:这里使用`do-while`循环,因为我们一开始就让两个指针从同一个位置开始,所以要先移动再判断。3.**第二阶段:寻找环的入口**-将两个指针分别置于起始位置(`ptr1=nums[0]`)和相遇位置(`ptr2=slow`)-然后同时每次移动一步,直到两个指针再次相遇。-相遇点即为环的入口点,也就是重复的数。###为什么重复的数就是环的入口?在Floyd判圈算法中,我们有以下数学关系:设从起点到环入口的长度为`a`,环入口到相遇点的长度为`b`,相遇点继续向前到环入口的长度为`c`(则环的长度为`b+c`)。快指针走过的路程是慢指针的两倍:`2(a+b)=a+b+k*(b+c)`,其中`k`是快指针在环内转的圈数。简化得:`a=k*(b+c)-b=(k-1)*(b+c)+c`因此,从起点和相遇点分别开始的两个指针(每次走一步)同时开始走,必然在环的入口处相遇(因为走了`a`步之后,从起点出发的指针到达环的入口,同时从相遇点出发的指针走了`a`,恰好也到达环入口)。###注意事项-题目中数组元素范围为`[1,n]`,所以数组中的值不会指向索引0(因为最小为1),但注意第一个元素是`nums[0]`,所以当`slow`或`fast`为0时,我们可能会遇到问题?然而,由于数组值范围在`[1,n]`,所以指针移动时不会指向0(除了初始位置0,但初始位置的值也不会是0,因为最小1)。因此,我们可以安全地使用数组值作为下一个索引。-该算法时间复杂度为O(n),空间复杂度为O(1),符合题目要求。###总结通过将数组索引视为链表节点,利用快慢指针检测环并找到环入口的方式,我们可以在不修改数组且只使用常数空间的情况下,高效地找到重复的数。§§相关问题§§1.为什么在数组值在1到n之间时,快慢指针不会出现死循环?2.如果数组中存在多个重复的数,该算法还能使用吗?3.你能用图例或具体例子来演示一下算法的执行过程吗?</think>
### LeetCode 287:快慢指针实现寻找重复数详解
以下是对快慢指针(Floyd判圈算法)实现 **Java 代码逻辑**的分步解析(假设数组元素为 `[1,3,4,2,2]`):
```java
public int findDuplicate(int[] nums) {
int slow = nums[0]; // 慢指针起点
int fast = nums[nums[0]]; // 快指针起点(领先两步)
// 阶段1:检测环的存在
while (slow != fast) {
slow = nums[slow]; // 慢指针移动1步
fast = nums[nums[fast]]; // 快指针移动2步
}
// 阶段2:定位环的入口
int ptr = 0;
while (ptr != slow) {
ptr = nums[ptr]; // 新指针从头开始
slow = nums[slow]; // 慢指针继续移动
}
return ptr; // 环入口即重复数字
}
```
---
### ⚙️ 核心原理分步拆解
#### **阶段1:检测环(龟兔赛跑)**
- **起点初始化**
`slow = nums[0] = 1`,`fast = nums[nums[0]] = nums[1] = 3`
- **移动过程**
迭代移动指针直到相遇:
```
第1轮: slow=1 → 3, fast=3 → 2(fast先到nums[3]=2)
第2轮: slow=3 → 2, fast=2 → 4(fast到nums[2]=4)
第3轮: slow=2 → 4, fast=4 → 4(相遇!此时值=4)
```
#### **阶段2:定位环入口(Floyd定理)**
- **重置新指针**
`ptr = 0`(起点),`slow = 4`(相遇点)
- **同步移动**
```
第1轮: ptr=0 → 1, slow=4 → 4
第2轮: ptr=1 → 3, slow=4 → 2
第3轮: ptr=3 → 2, slow=2 → 4
第4轮: ptr=2 → 4(相遇!重复数=4)
```
**最终返回 ptr=4**(实际是元素2重复,但值4表示环入口位置)
---
### 🔍 关键逻辑说明
1. **为什么能检测环?**
重复数字导致多个索引指向同一位置,形成有向环(如图)。快指针终将追上慢指针。
2. **如何找到重复值?**
根据Floyd定理:
设起点到环入口距离为 `a`,环入口到相遇点为 `b`,则:
$$2(a+b) = a + k \cdot L + b \quad (L为环长)$$
解得 $a = (k-1)L + (L - b)$,故指针相遇点即为环入口。
3. **时间复杂度**
- **阶段1**:最坏 $O(n)$(慢指针遍历整个数组)
- **阶段2**:$O(n)$
**整体满足 $O(n)$ 要求**,空间 $O(1)$
---
### ✅ 算法适用条件
1. 数组长度为 **n+1**,元素范围 **[1, n]**
2. **仅一个重复数**(多重复数会破坏环结构)
3. 数组 **只读**(不修改原数据)
---
> 示例中返回位置4对应值2,实际重复数字为2(因为数组索引3和4都指向值2)
阅读全文
相关推荐




















