算法通关村第一关——单链表中环的问题

目录

1.如何确定链表中存在环

2.若存在环,如何确定环的入口


环是链表中的经典问题,问题确定是否存在环并不算难,确定环的入口会比较复杂,但这两个一般都会同时出现,所以,两个都必须掌握。

1.如何确定链表中存在环

leetcode力扣 141icon-default.png?t=N7T8https://leetcode.cn/problems/linked-list-cycle/description/题目:给你一个单链表的头结点,判断链表中是否有环。

示例:        输入:head=

                   输出:true

方法一:Hash

        最容易想到的方法是遍历所有节点,每次遍历到一个节点时,判断该节点此前是否被访问过。

        具体地,我们可以使用哈希表来存储所有已经访问过的节点。每次我们到达一个节点,如果该节点已经存在于哈希表中,则说明该链表是环形链表,否则就将该节点加入哈希表中。重复这一过程,直到我们遍历完整个链表即可。若存在环,一定会发生碰撞,发生碰撞的位置也就是环的入口,即上图node(2)。

        但这还不够优秀,如何使用O(1)的空间完成呢?见下一个方法

方法二:双指针法

        使用快慢指针fast,slow,两者一开始都指向第一个结点,使用while循环,fast一次走两步,slow一次走一步,循环判断条件为while(fast !=NULL &&fast->next !=NULL),

        如果没有环,那么fast指针一定会在某时刻指向NULL或fast的下一个结点为NULL。则退出循环。

        若存在环,那么fast上面的两种情况一定不会发生,但是,随着时间的推移,fast与slow指针一定会在某时刻相遇,所以在循环体内部增加if判断。也许你会问,fast一次走两步,刚刚好跳过slow了怎么办,下面我们画图分析

在fast接近slow时有以下两种情况:

情况1:

fast在node(1)与slow在node(2),相差1步,这时slow走一步,fast走两步,刚好在node(3)相遇,退出循环。

情况2:

fast在node(1)与slow在node(3),相差2步,这时slow走一步,fast走两步,就变成了上面的情况1,fast与slow还是会相遇,退出循环。

所以,若存在环,使用快慢指针法,快慢指针一定会相遇。

bool hasCycle(struct ListNode *head) {
        if(head == NULL || head->next == NULL) return NULL;
        struct ListNode* slow = head;
        struct ListNode* fast = head;
        slow=slow->next;
        fast=fast->next->next;
        
        
        //若fast遍历至链表尾,则说明不存在环
        while(fast !=NULL &&fast->next !=NULL){
            
            //若fast与slow相遇,则说明存在环
            if(fast == slow){
                return true;
            }
             slow=slow->next;
             fast=fast->next->next;
        }

        return false;
    }

2.若存在环,如何确定环的入口

leetcode力扣 142icon-default.png?t=N7T8https://leetcode.cn/problems/linked-list-cycle-ii/description/        确定环入口也是使用快慢指针法,不过是在fast与slow相交后,使slow重新指向头结点,然后两个指针,同时开始一次走一步遍历,最后,两指针再次相交的结点就是环的入口。为什么呢?下面来分析

        不难知道,在两指针第一次相交时,fast肯定已经在环中饶了n圈了。下面来分情况画图分析   

        x为head,y为环入口,z为fast与slow第一次相交的结点,A,B,C分别为xy,yz,zy的距离。

n=1,也即fast已经在环中饶了一圈了,且fast的速度使slow的两倍,所以A+B+C+B = 2(A+B)  即 A=C,所以,第一次相遇后,slow重新指向头结点两指针都一次走一步,最终会在y结点相遇。

n>1,也即fast已经在环中饶了n圈了,有 A+N(B+C)+B = 2(A+B),即 A=N(B+C)-B,B+C=length=环的长度,所以 A=N*length-B,所以第一次相遇后,slow重新指向头结点两指针都一次走一步,最终会在y结点相遇。

综上所述,快慢指针第二次相交的结点就是环的入口。

struct ListNode *detectCycle(ListNode *head) {
         if(head == NULL || head->next == NULL) return NULL;
        struct ListNode* slow = head;
        struct ListNode* fast = head;
        slow=slow->next;
        fast=fast->next->next;
        
        
        //若fast遍历至链表尾,则说明不存在环
        while(fast !=NULL &&fast->next !=NULL){
            
            //若fast与slow相遇,则说明存在环,开始找环入口
            if(fast == slow){
                slow=head;
                while(fast!=slow){
                    fast=fast->next;
                    slow=slow->next;
                }
                //返回环入口
                return fast;
            }
             slow=slow->next;
             fast=fast->next->next;
        }

        return NULL;
    }

总结:

        1.确定链表中是否存在环,使用双指针快慢指针法,若存在环,则两指针一定会相遇。

        2.如何确定环的入口,先确定链表中有环,然后将slow重新指向head,两指针同时一次走一步开始遍历,最后两指针再次相遇的结点就是环的入口了。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值