No.246 - LeetCode[287] Find the Duplicate Number - 数组值在1-n内找唯一重复值

这篇博客介绍了如何使用单向链表和环的概念解决LeetCode问题287,通过分析步长差找到链表中的重复元素。博主展示了如何利用两个指针,一个每次走一步,另一个走两步,当它们相遇时,重复值的索引差即为环的长度,从而找出重复数字。

这个数组可以形成有环的单向链表,数组下标是当前点,数组值是指向下一节点的指针

假设环外长d,环长l,相碰处距汇点x
则fast = d+l+x,slow = d+x,且fast = 2slow
解得x = l - d,即slow再走d步就是汇点。

第一次初始化假设f和s都各先走了一步,
s= 0 , s = nums[s] 即 s = nums[0]
f = 0, f = nums[nums[f]] 即 f = nums[nums[0]]
故f第二次初始化为0

/*
 * @lc app=leetcode id=287 lang=cpp
 *
 * [287] Find the Duplicate Number
 */

// @lc code=start
class Solution {
public:
    // 因为值都在数组下标范围内,所以刚好可以形成单向链表
    // 重复值会形成链表的环
    int findDuplicate(vector<int>& nums) {
        int N = nums.size();
        int s = nums[0]; // 一个步长为1
        int f = nums[nums[0]]; // 一个步长为2
        // 第一次循环,若有环,会在环内l-d处相遇
        for(int i=0;i<N;i++){
            if(s == f) break;
            s = nums[s];
            f = nums[nums[f]];
        }
        // 第二次循环,f和s同时1步长走d步,会再次碰到就是汇合处
        f = 0; // f从0开始才是d步,从nums[0]开始是d-1步,如上
        for(int i=0;i<N;i++){
            if(f == s) break;
            f = nums[f];
            s = nums[s];
        }
        return s;
    }
};
// @lc code=end
<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)
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值