Leetcode NO.287 Find The Duplicate Number

该博客讨论了一个编程问题,即在一个包含1到n的整数数组中找到唯一的重复数字。提供了两种解决方案:二分查找和快慢指针。两种方法的时间复杂度分别为O(nlogn)和O(n),空间复杂度为O(1)。示例用例和代码详细说明了算法的实现过程。

1.问题描述

给定一个包含 n + 1 个整数的数组 nums ,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个重复的整数。

假设 nums 只有 一个重复的整数 ,找出 这个重复的数 。

你设计的解决方案必须不修改数组 nums 且只用常量级 O(1) 的额外空间。

2.测试用例

示例 1:

​ 输入:nums = [1,3,4,2,2]
​ 输出:2

示例 2:

​ 输入:nums = [3,1,3,4,2]
​ 输出:3

示例 3:

​ 输入:nums = [1,1]
​ 输出:1

示例 4:

​ 输入:nums = [1,1,2]
​ 输出:1

3.提示

  • 1 <= n <= 105
  • nums.length == n + 1
  • 1 <= nums[i] <= n
  • nums 中 只有一个整数 出现 两次或多次 ,其余整数均只出现 一次

4.代码

1.binary search
code
public int findDuplicate4(int[] nums) {
        int n = nums.length;
        if (n < 1) {
            return -1;
        }
        //取值范围是从1 -N
        //从1开始
        int l = 1;
        //nums.length 是 n + 1; 结束到N
        int r = nums.length - 1;

        while (l <= r) {
            int mid = (l + r) / 2;
            int count = 0;
            for (int num : nums) {
                if (num <= mid) {
                    count++;
                }
            }
            if (count <= mid) {
                l = mid + 1;
            } else {
                r = mid;
            }
            if (l == r) {
                return l;
            }
        }
        return -1;
}
复杂度

时间 O(nlogn)
空间 O(1)

2. 快慢指针
code
public int findDuplicate(int[] nums) {
        int low = 0, fast = 0;

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


        int p1 = 0, p2 = low;
        do {
            p1 = nums[p1];
            p2 = nums[p2];
        } while (p1 != p2);

        return p1;
}
复杂度

时间 O(n)
空间 O(1)

<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、付费专栏及课程。

余额充值