Leetcode287. 寻找重复数

这篇博客介绍了LeetCode287题的两种解决方案:二分法和双指针法。二分法利用数组元素的单调性找到重复的数;双指针法通过模拟链表环路找到重复的整数。详细阐述了两种方法的思路,并提供了Java代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Leetcode287. 寻找重复数

题目:
给定一个包含 n + 1 个整数的数组 nums ,其数字都在 [1, n] 范围内(包括 1 和 n),可知至少存在一个重复的整数。
假设 nums 只有 一个重复的整数 ,返回 这个重复的数 。
你设计的解决方案必须 不修改 数组 nums 且只用常量级 O(1) 的额外空间。

题解:
方案一: 二分法
我们定义 c n t [ i ] cnt[i] cnt[i] 表示 n u m s nums nums 数组中小于等于 i i i的数有多少个,假设我们重复的数是 t a r g e t target target,那么 [ 1 , t a r g e t − 1 ] [1,target−1] [1,target1]里的所有数满足 c n t [ i ] ≤ i cnt[i]≤i cnt[i]i [ t a r g e t , n ] [target,n] [target,n] 里的所有数满足 c n t [ i ] > i cnt[i]>i cnt[i]>i,具有单调性。

以示例 1 为例,我们列出每个数字的 c n t cnt cnt 值:

nums1234
cnt1345

示例中重复的整数是 2,我们可以看到 [ 1 , 1 ] [1,1] [1,1] 中的数满足 c n t [ i ] ≤ i cnt[i]≤i cnt[i]i [ 2 , 4 ] [2,4] [2,4] 中的数满足 c n t [ i ] > i cnt[i]>i cnt[i]>i

如果知道 c n t [ ] cnt[] cnt[] 数组随数字 i i i 逐渐增大具有单调性(即 t a r g e t target target c n t [ i ] ≤ i cnt[i]≤i cnt[i]i t a r g e t target target c n t [ i ] > i cnt[i]>i cnt[i]>i),那么我们就可以直接利用二分查找来找到重复的数。

但这个性质一定是正确的吗?考虑 n u m s nums nums 数组一共有 n + 1 n+1 n+1 个位置,我们填入的数字都在 [ 1 , n ] [1,n] [1,n] 间,有且只有一个数重复放了两次以上。对于所有测试用例,考虑以下两种情况:

  • 如果测试用例的数组中 t a r g e t target target出现了两次,其余的数各出现了一次,这个时候肯定满足上文提及的性质,因为小于 t a r g e t target target 的数 i i i 满足 c n t [ i ] = i cnt[i]=i cnt[i]=i,大于等于 t a r g e t target target 的数 j j j 满足 c n t [ j ] = j + 1 cnt[j]=j+1 cnt[j]=j+1
  • 如果测试用例的数组中 t a r g e t target target 出现了三次及以上,那么必然有一些数不在 n u m s nums nums 数组中了,这个时候相当于我们用 t a r g e t target target去替换了这些数,我们考虑替换的时候对 c n t [ ] cnt[] cnt[] 数组的影响。如果替换的数 i i i 小于
    t a r g e t target target ,那么 [ i , t a r g e t − 1 ] [i,target−1] [i,target1] c n t cnt cnt 值均减一,其他不变,满足条件。如果替换的数 j j j大于等于
    t a r g e t target target,那么 [ t a r g e t , j − 1 ] [target,j−1] [target,j1] c n t cnt cnt 值均加一,其他不变,亦满足条件。

方案二:双指针
使用环形链表II的方法解题(142环形链表II),使用 142 题的思想来解决此题的关键是要理解如何将输入的数组看作为链表。
首先明确前提,整数的数组 nums 中的数字范围是 [1,n]。考虑一下两种情况:

如果数组中没有重复的数,以数组 [1,3,4,2]为例,我们将数组下标 n 和数 n u m s [ n ] nums[n] nums[n] 建立一个映射关系 f ( n ) f(n) f(n)
其映射关系 n − > f ( n ) n->f(n) n>f(n)为:
0->1
1->3
2->4
3->2
我们从下标为 0 出发,根据 f ( n ) f(n) f(n)计算出一个值,以这个值为新的下标,再用这个函数计算,以此类推,直到下标超界。这样可以产生一个类似链表一样的序列。
0->1->3->2->4->null

如果数组中有重复的数,以数组 [1,3,4,2,2] 为例,我们将数组下标 n 和数 n u m s [ n ] nums[n] nums[n] 建立一个映射关系 f ( n ) f(n) f(n)
其映射关系 n − > f ( n ) n->f(n) n>f(n) 为:
0->1
1->3
2->4
3->2
4->2
同样的,我们从下标为 0 出发,根据 f ( n ) f(n) f(n)计算出一个值,以这个值为新的下标,再用这个函数计算,以此类推产生一个类似链表一样的序列。
0->1->3->2->4->2->4->2->……
这里 2->4 是一个循环,那么这个链表可以抽象为下图:
在这里插入图片描述

从理论上讲,数组中如果有重复的数,那么就会产生多对一的映射,这样,形成的链表就一定会有环路了,

综上
1.数组中有一个重复的整数 <> 链表中存在环
2.找到数组中的重复整数 <
> 找到链表的环入口

至此,问题转换为 142 题。那么针对此题,快、慢指针该如何走呢。根据上述数组转链表的映射关系,可推出
142 题中慢指针走一步 slow = slow.next ==> 本题 slow = nums[slow]
142 题中快指针走两步 fast = fast.next.next ==> 本题 fast = nums[nums[fast]]

java代码:

 /**
     * 二分法
     *
     * @param nums
     * @return
     */
    public static int findDuplicate(int[] nums) {
        int len = nums.length;
        int left = 1, right = len - 1;
        int res = -1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            int cnt = 0;
            for (int i = 0; i < len; i++) {
                if (nums[i] <= mid) {
                    cnt++;
                }
            }
            if (cnt <= mid) {
                left = mid + 1;
            } else {
                right = mid - 1;
                res = mid;
            }
        }
        return res;
    }


    /**
     * 双指针
     *
     * @param nums
     * @return
     */
    public static int findDuplicate2(int[] nums) {
        int fast = 0;
        int slow = 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;
    }
### Python 实现 LeetCode 287 寻找数组内的复数 对于在Python中解决寻找数组复数字的问题,可以采用多种方法来处理这个问题。一种常见的高效解法是利用二分查找或者快慢指针的方法。 #### 方法一:二分查找 通过设定左右边界并逐步缩小范围直到找到复元素的位置。这种方法不需要额外的空间开销,并且能够有效地减少时间复杂度。 ```python def findDuplicate(nums): low = 1 high = len(nums) - 1 while low <= high: mid = (low + high) >> 1 count = sum(num <= mid for num in nums) if count > mid: duplicate = mid high = mid - 1 else: low = mid + 1 return duplicate ``` 此段代码实现了基于二分查找的解决方案[^3]。 #### 方法二:快慢指针(Floyd 判圈算法) 该方法模仿链表检测环路的思想,在不改变输入数组的情况下工作。它使用两个指针——一个移动速度快于另一个;当它们相遇时,则说明存在循环即找到了复项。 ```python def findDuplicate(nums): tortoise = hare = nums[0] # Phase 1: Find the intersection point of two runners. while True: tortoise = nums[tortoise] hare = nums[nums[hare]] if tortoise == hare: break # Phase 2: Find the entrance to cycle (duplicate element). tortoise = nums[0] while tortoise != hare: tortoise = nums[tortoise] hare = nums[hare] return hare ``` 上述代码展示了如何应用 Floyd 的判圈算法来定位复数值。 这两种方式都能够在 O(n log n) 或者更优的时间复杂度下完成任务,并且不会修改原始数据结构。如果考虑空间效率的话,建议优先尝试这些原地工作的算法
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值