Find the Duplicate Number

Find the Duplicate Number

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(n2).
There is only one duplicate number in the array, but it could be repeated more than once.

分析1

比较容易想到的是O(nlogn)的算法,根据题意,所有的数字小于等于n,大于等于1,可以通过二分查找的方法,首先查找小于中间数字的数量,如果重复的数字比中间数字小,那么小于中间数字的数量肯定大于中间数字,反之则重复的数字比中间的数字大

代码1

class Solution {
public:
    int findDuplicate(vector<int>& nums) {
        int high = nums.size()-1;
        int low = 1;

        while (low < high){
            int mid = (high+low)/2;
            int count = 0;
            for (int num: nums){
                if (num <= mid) count++;
            }

            if (count <= mid) low = mid+1;
            else high = mid;
        }

        return low;
    }
};

分析2

题目中最关键的信息是,所有的数字介于1到n之间,并且总数为n+1,那么每一个位置存储的数字就可以看作是数组中的位置,那么整个数组就可以看成是一个链表,头节点是nums[0],如果存在重复的数字,那就是有不同的节点指向同一个位置,即存在环,所以整个问题就转化成了单向链表求环的问题,并且,环相交的点就是重复的数字。

代码2

class Solution {
public:
    int findDuplicate(vector<int>& nums) {
        int fast = nums[nums[0]];
        int low = nums[0];

        while (low != fast){
            low = nums[low];
            fast = nums[nums[fast]];
        }

        low = 0;
        while (low != fast){
            low = nums[low];
            fast = nums[fast];
        }

        return low;
    }
};
<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)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值