快慢指针(LeetCode寻找重复数),弗洛伊德的乌龟和兔子

写此篇博客在于总结,记忆之用,欢迎评论补充。
弗洛伊德的乌龟和兔子,即快慢指针。
对于LeetCode287题,寻找重复数,题目如下:

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

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

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

这道题我们有三种思路,
方法一:排序
如果对数字进行排序,则任何重复的数字都将与排序后的数组相邻。

class Solution {
    public int findDuplicate(int[] nums) {
        Arrays.sort(nums);
        for (int i = 1; i < nums.length; i++) {
            if (nums[i] == nums[i-1]) {
                return nums[i];
            }
        }
        return -1;
    }
}

复杂度分析
时间复杂度:O(nlgn)O(nlgn)。排序调用在 Java 中花费 O(nlgn)时间,因此它支配后续的线性扫描。
空间复杂度:O(1)orO(n),在这里,我们对 nums 进行排序,因此内存大小是恒定的。如果我们不能修改输入数组,那么我们必须为 nums 的副本分配线性空间,并对其进行排序。
方法二:集合
集合就是利用set集合的性质,即set集合不能出现重复的元素

class Solution {
    public int findDuplicate(int[] nums) {
        Set<Integer> seen = new HashSet<Integer>();
        for (int num : nums) {
            if (seen.contains(num)) {
                return num;
            }
            seen.add(num);
        }
        return -1;
    }
}

时间复杂度O(n):JAVA依赖于底层的哈希表,所以插入和查找有固定的复杂度。因此,该算法是线性的,因为它由一个执行N次恒定工作的for循环组成。
空间复杂度O(n):在最坏的情况下,重复元素出现两次,其中一次出现在数组索引n-1处,这种情况下,seen将包含n-1个不同的值,因此占用O(n)的存储空间。
方法三:弗洛伊德的乌龟和兔子(循环检测)
意此题的题干,每个数组位的和他的数字是链表的体现,至少存在一个整数是重复的,说明存在环
如果我们对于nums进行这样的解释,即对于没对索引i和值Vi而言,"下一个"Vj位于索引Vi处,可以将此问题减少到循环检测。
步骤大概为:找快慢指针交点,再把慢指针移到表头,然后两个表在此相遇的点就是环的

class Solution {
    public int findDuplicate(int[] nums) {
        // Find the intersection point of the two runners.
        int tortoise = nums[0];
        int hare = nums[0];
        do {
            tortoise = nums[tortoise];//tortoise走一步
            hare = nums[nums[hare]];//hare走两步
        } while (tortoise != hare);//如果他们不相等

        // Find the "entrance" to the cycle.
        int ptr1 = nums[0];//因为数组从0开始,所以nums是从0开始的
        int ptr2 = tortoise;
        while (ptr1 != ptr2) {
            ptr1 = nums[ptr1];
            ptr2 = nums[ptr2];
        }
        return ptr1;
    }
}

时间复杂度:O(n)
空间复杂度:O(1)

链接:https://leetcode-cn.com/problems/find-the-duplicate-number/solution/xun-zhao-zhong-fu-shu-by-leetcode/来源:力扣(LeetCode)
oyd(Floyd cycle detection)

问题:如何检测一个链表是否有环,如果有,那么如何确定环的起点?如何确定环的长度?

时间复杂度:O(n) (高效率)
空间复杂度:O(1)
此算法又成为龟兔赛跑算法,基本思想是:

好比两个人在赛跑,A的速度快,B的速度慢,经过一定时间后,A总是会和B相遇,且相遇时A跑过的总距离减去B跑过的总距离一定是圈长的n倍(初中数学题……)。

弗洛伊德(Floyd )使用了两个指针,一个慢指针(龟)每次前进一步,快指针(兔)指针每次前进两步(两步或多步效果是等价的,只要一个比另一个快就行。但是如果移动步数增加,算法的复杂度可能增加)。如果两者在链表头以外(不包含开始情况)的某一点相遇(即相等)了,那么说明链表有环,否则,如果(快指针)到达了链表的结尾(如果存在结尾,肯定无环),那么说明没环。

环的检测从上面的解释理解起来应该没有问题。接下来我们来看一下如何确定环的起点,这也是Floyd解法的第二部分。方法是将慢指针(或快指针)移到链表起点,然后两者同时移动,每次移动一步,那么两者相遇的地方就是环的起点。

下面给出论证过程:

假设一个链表是下面这样的:

在这里插入图片描述
设环长为n,非环形部分长度为m,当第一次相遇时显然slow指针行走了 m+An+k(A表示slow行走了A圈。附:An 是因为如果环够大,则他们的相遇需要经过好几环才相遇)。fast行走了 m+B*n+k。

上面我们说了slow每次行走一步,fast每次行走两步,则在同一时间,fast行走的路程是slow的两倍。假设slow行走的路程为S,则fast行走的路程为2S。

用fast减去slow可得:

S=(B-A)*n

很显然这意味着当slow和fast相遇时他们走过的路程都为圈长的倍数。

接下来,将slow移动到起点位置,如下图:
在这里插入图片描述

初始化额外的两个指针: ptr1 ,指向链表的头, ptr2 指向相遇点。然后,
每次将它们往前移动一步,直到它们相遇,它们相遇的点就是环的入口

在这里插入图片描述
我们利用已知的条件:慢指针移动 1 步,快指针移动 2 步,来说明它们相遇在环的入口处:(下面证明中的 tortoise 表示慢指针,hare 表示快指针)

我们注意到在这里,环的起点即为重复数
在这里插入图片描述

原文链接:https://blog.csdn.net/u012534831/article/details/74231581

本文为东拼西凑来的,可以点开原文看具体内容呦。
https://www.jianshu.com/p/21b4b8d7d31b这个链接有快慢指针的具体用法
快慢指针有大概三个作用:找中间值,删除倒数第n个节点,寻找环。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值