Java刷题笔记13:寻找重复数

本文介绍了如何使用快慢指针和Floyd判圈法解决LeetCode中寻找数组中重复数字的问题。当给定一个包含n+1个1到n的整数数组,利用数组构造链表模拟并寻找链表环的入口,从而找到重复的数字。

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

题目描述

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

示例

示例1:

输入: [1,3,4,2,2]
输出: 2

示例2:

输入: [3,1,3,4,2]
输出: 3

  • 不能更改原数组(假设数组是只读的)。
  • 只能使用额外的 O(1) 的空间。
  • 时间复杂度小于 O(n2) 。
  • 数组中只有一个重复的数字,但它可能不止重复出现一次

快慢指针

若是没有规定只能使用O(1)的时间那么这题很容易,使用哈希表可以完美解决,且最坏时间复杂度仅为O(n)。但为了满足题目的要求那就只能另寻它法了。
仔细看了一下题目,发现题目给的数组很特殊。它只有n+1个数,但是包括了1~n的所有整数。有经验的同学可能看出来了,这种数组是可以模拟链表的。把数组内的元素当做下一个元素的坐标,也就是说数组内的元素相当于next指针。如示例1中的数组就可以模拟如下链表:
在这里插入图片描述
上图中由于下标2->下标4,下标3和下标4均指向下标2(指针相同意味着数组元素相同),所以它们的映射关系可以合并为:2->4,4->2。
仔细思考就可以发现,在此题中的数组出现重复数字时那么就一定会出现环,而这个环的入口就是那些重复的数字。所以问题转换为了寻找链表环的入口。
对于寻找环入口,有一个非常简便的方法:Floyd判圈法。
设置两个指针,遍历链表,一个指针每次走一步,一个指针每次走两步。若是链表存在环,那么在慢指针走到尾部前它们一定会相遇。简单说明一下,当链表中有环时,快指针一定会先进入环,并且一直在环中循环等待慢指针也进入环。假设环的周长为L,考虑极端情况,在慢指针到达环入口时,快指针恰好在慢指针的前面一步。此时慢指针与快指针的距离为L-1.每走一步它们的距离就会减一,而慢指针重新走回入口处需要L步,所以在慢指针走完之前快指针一定能追上它。

接下来简单证明碰撞点到环形入口的长度加上L的一个整数倍恰好等于起点到入口的长度。假设起点到环入口长度为M,入口到碰撞点B的长度为N,环形周长为L。那么在碰撞时慢指针一共走了slow_step=M+N.快指针一共走了fast_step=M+N+L*k (k∈Z*)=2*slow_step。所以有M+N=L*k,M=L*k-N,得证。
L*k-N恰好就是碰撞点到环形入口的距离(严格一点不能说是距离,因为K可以变化,改成碰撞点到入口所需要的走的步数可能好接受一点,K+1就多走一圈)。
代码实现:当碰撞后将一个指针置于起点,然后每次循环所有的指针向前一步,下一次的碰撞点即为入口,这样完全不需要关注指针在环里走了几圈。

 public static int findDuplicate2(int[] nums) { //快慢指针,寻找环入口
        //在环中两个指针每走一次,距离就会减一,而两者最大距离为周长l-1,所以若有环则一定会相遇。
        int slow = nums[0], fast = nums[nums[0]];
        while (slow != fast) {
            slow = nums[slow];
            fast = nums[nums[fast]];
        }
        slow = nums[0];
        fast = nums[fast];
        while (slow != fast) {
            slow = nums[slow];
            fast = nums[fast];
        }
        return fast;
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值