环形链表 or 快慢指针

环形链表 OR 快慢指针

在这里插入图片描述

判断环是否存在

如上图所示,在一个单链表中存在一个环,判断这个环是否存在。
一个比较容易想到的方法是哈希表,每遍历一个节点,先判断其是否已经在哈希表中,如果在,说明存在环,结束;否则就加入哈希表,知道判断存在环或者遍历完所有的节点。

但是上面的做法有一定的空间开销,如果链表节点数足够大,容易造成一定的空间开销。有些算法题也要求使用原地算法来解决,因此要使用双指针的方法;

Floyd判圈算法(龟兔赛跑算法)

相像一下【乌龟】和【兔子】在链表上移动,【兔子】跑的快,假定每次走两步,【乌龟】跑得慢,假定每次走一步。
当两者从链表上同一个节点开始移动时,如果没有环的存在,那么【乌龟】永远不可能追上【兔子】;
如果有环的存在,那么【兔子】一定会先于【乌龟】进入环,并且一直在环内移动,等到【乌龟】进入环时,由于【兔子】跑的快,所以会把【乌龟】套圈,在某一个时刻二者在环内相遇;

根据以上思路来解决问题,我们可以定义两个指针,慢指针slow和快指针fast,慢指针就是上面的【乌龟】,每次移动一步,快指针就是上面的【兔子】,每次移动两步;
初始时,【乌龟】和【兔子】在同一个出发点,每一轮移动,慢指针走一步,快指针走两步,然后开始遍历指针;如果在移动过程中,快指针反过来追上慢指针,那么就说明该链表中存在环;否则快指针将到达链表尾部,该链表不为环形链表。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

        ListNode *fast = head;
        ListNode *slow = head;
        while (true)
        {
            if (fast == nullptr || fast->next == nullptr)
                return nullptr;
            slow = slow->next;
            fast = fast->next->next;
            if (slow == fast)
                return true
        }
        return false;

时间复杂度是O(N),N是节点个数。

  • 当链表不存在环时,快指针将先于慢指针到达链表尾部,链表中的每个节点至多被访问两次;
  • 当链表中存在环时,每一轮移动后,快慢指针的距离将减小1,二者间的最大距离是环的长度,因此最多移动N轮(N个节点都属于环)

进阶版:找到环的入口节点

最简单的方法还是哈希表,但是和上面所说一样,空间开销大,不符合某些题目要求的原地算法。

在上面的基础上,我们已经可以判断环的存在,以及找到二者相遇的结点。
我们来考虑这样一个问题:
假设环是存在的,其中 a 个节点属于环之前的节点,b 个节点属于环中的节点,以上面的图为例,a=3, b=4;

  • 当【乌龟】和【兔子】相遇时,【兔子】所走的步数是【乌龟】的两倍,即 f=2s
  • 【兔子】比【乌龟】多走了 n 个环的长度,即f=s+nb(两者都走过了 a 步。然后在环内绕圈直到相遇,相遇时【兔子】比【乌龟】多走环的长度的整数倍
  • 两式相减得到:f=2nbs=nb,即【兔子】和【乌龟】分别走了 2n 个环和 n 个环的长度

目前情况分析:

如果让指针从链表头部一直向前走并统计步数 k,那么所有 走到链表入口节点时的步数 是:k=a+nb(先走 a 步到入口节点,之后每绕 1 圈环( b 步)都会再次到入口节点)。
而目前,slow 指针走过的步数为 nb 步。因此,我们只要想办法让 slow 再走 a步停下来,就可以到环的入口。
但是我们不知道 a的值,该怎么办?依然是使用双指针法。我们构建一个指针,此指针需要有以下性质:此指针和 slow 一起向前走 a 步后,两者在入口节点重合。那么从哪里走到入口节点需要 a 步?答案是链表头部 head 。
双指针第二次相遇:
slow指针 位置不变 ,将 fast 指针重新 指向链表头部节点 ;slow 和 fast 同时每轮向前走 1 步;
TIPS:此时 f=0,s=nb ;
当 fast 指针走到f=a 步时,slow 指针走到步s=a+nb ,此时 两指针重合,并同时指向链表环入口 。
第二次相遇后,返回 slow 指针指向的节点即可。
在这里插入图片描述

    ListNode *detectCycle(ListNode *head)
    {

        ListNode *fast = head;
        ListNode *slow = head;
        while (true)
        {
            if (fast == nullptr || fast->next == nullptr)
                return nullptr;
            slow = slow->next;
            fast = fast->next->next;
            if (slow == fast)
                break;
        }
        fast = head;
        while (fast != slow)
        {
            slow = slow->next;
            fast = fast->next;
        }
        return fast;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值