【环形链表】 判断环形? 返回入环节点? 两种问题具体思路,文中包含多种解法(附完整代码)


前言

本文将介绍两种环形链表的问题——判断环形链表 和 返回入环节点

对于这两个问题,都将各自介绍两种不同的思路方法。

【暴力法】【双指针】

【哈希遍历】【快慢指针】


一、环形链表是什么?

请添加图片描述

环形链表是一种特殊的链表结构,其中至少存在一个节点,它的指针指向链表中的其他节点,从而形成一个环状。


二、环形链表Ⅰ(判断环形链表)

在这里我们引入具体的题目,方便大家更好地理解环形链表问题。

题目链接: [点击链接] Leetcode 141. 环形链表

请添加图片描述
这一类环形链表问题,要求给出头节点,判断是否为环形链表即可。


1.方法一(暴力)

这道题很有趣,并且值得注意的是给出了节点数量的范围。

既然给出了节点数,那么我们就可以利用环形链表的特点(有进无出),来进行循环,环形链表一旦开始循环,就无法自己跳出循环。

所以跳出循环的就不是环形链表。

代码如下:

class Solution {
public:
    bool hasCycle(ListNode *head) {
       for(int i=0;i<=10000;i++){//这里i<=10000是因为节点最多有10000个
            if(head==nullptr){
                return false;
            }
            head=head->next;
        }
        return true;
    }
};

2.方法二(双指针)

可以采用快慢指针的方式,将链表的所有节点全部走过,一旦,两个指针再相遇,说明为环形链表。

快慢指针主要利用,慢指针每次移动慢,快指针每次移动快,如果是非环形的链表,那么快指针会一直比慢指针快,如果最初快指针就在慢指针前面,那么两个指针将永不相遇。

反之,如果两个指针相遇了说明了什么呢?

说明链表是环形的,快指针又回到了慢指针的后面。

代码如下:

class Solution {
public:
    bool hasCycle(ListNode* head) {
        ListNode* slow = head;//慢指针
        ListNode* fast = head;//快指针
        while (fast && fast->next) {
            slow = slow->next;//每次移动一次
            fast = fast->next->next;//每次移动两次
            if (slow == fast) {//相遇
                return true;
            }
        }
        return false;
    }
};

三、环形链表Ⅱ(返回入环节点)

这里我们引入另一种环形链表的题。

题目链接: [点击跳转] Leetcode 142. 环形链表 II

请添加图片描述
这道题则是在判断环形链表后,求解从哪里开始入环。


1.方法一(哈希遍历)

通过维护一个哈希表,将链表中的节点作为key存入map中,如果这个节点没出现过就是默认的false,如果出现过就是true。

因此,我们可以设置一个循环,这个循环只有false时(新节点)才进行下去,当检测到true(说明之前存过这个节点,此时正是进入环的时刻),跳出循环,返回当前节点。

代码如下:

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        map<ListNode*,bool> maph;
        while (maph.find(head) == maph.end()){//找到已有节点
            if(head==nullptr||head->next==nullptr){//判断环形
                return NULL;
            }   
            maph.insert({head,true});//将节点插入
            head=head->next;
        }
        return head;      
    }
};

注意:不要忘了判断一下是否为环形链表哦!


2.方法二(快慢指针)

这道题同样可以用快慢指针解决。

观察下面的一个环形链表(链表中的节点数未知):

  			   	 -------
  			   /		 \
(slow)       / 		       \
A-----------B			    -
(fast)		 \	           /
			   \		 /
			     -------

假设我们在节点A(也就是头节点)设置一个慢指针slow,和一个快指针fast。

当slow走到节点B时,我们可以确定fast一定已经在环上了(因为fast更快)。

  			   		(fast)
  			   	 -----C-
  			   /		 \
             / 		       \
A-----------B(slow)			-
			 \	           /
			   \		 /
			     -------

如上图,我们假设slow在节点B时(第一次到节点B),fast在节点C。

但我们不知道slow第一次到B时,fast已经在环上走了几圈(因为AB长度未知,环的周长也未知),我们只能假设fast此时已经在环上走了n圈(n>=0)。

快慢指针一定会在某一时刻相遇(关于为什么相遇,在 环形链表Ⅰ方法二中已经解答),那么会在什么时刻相遇呢?

我们用BC来表示环的左上部分长度,用CB来表示环的右下部分长度

  			   		(fast)
  			BC   -----C-
  			   /		 \
             / 		       \
A-----------B(slow)			-
			 \	           /
			   \		 /  CB
			     -------

如上图,当相遇时一定会有:
2*(AB+slow在环上走的长度)==AB+fast在环上走的长度

AB等于什么呢?
2AB==AB+BC+n(BC+CB)
当slow第一次走到B时,fast走到C,等式左侧是slow走的长度的2倍,右侧是fast走的总长度,二者是相等的,所以能得到这个等式。
可以推断出: AB=BC+n*(BC+CB)

  			   		(fast)
  			   	 -----C-
  			   /		 \
             / 		       \
A-----------B(slow)			-
			 \	           /
			   \		 /
			     -------

观察上图,我们可以直观的推断出:
slow再走长度为(CB)的路径后,就会和fast相遇。
因为此时的fast要追上slow,隔着(CB),而fast速度是slow的2倍,所以fast走2*(CB),slow走(CB),二者刚好相遇。

假设,slow和fast相遇再节点D,如下:

  			   	 -----C-
  			   /		 \
             / 		       \
A-----------B				-
			 \	           /	
		    BD \	     /
			     ------D(slow)(fast)

此时BC+CD就等于(CB)

我们发现CB还可以用CD+DB来表示呀

推出:BC+CD==CD+DB
推出:BC=DB

之前我们曾得到: AB=BC+n*(BC+CB)

那么可以得到:AB=DB+n*(BC+CB)

  			   	 -----C-
  			   /		 \
(rot)        / 		       \
A-----------B				-
			 \	           /	
		  BD   \	     /
			     ------D(slow)

重点来了,我们在节点A再定义一个指针rot 指向A,rot的速度与slow是一样的。

我们发现rot走到节点B,路径长度是AB,由于二者速度相同,slow走(DB+n*(BC+CB))长度,刚好slow也走到节点B,这时rot与slow相遇了。

我们返回这个时刻相遇节点即可。

代码如下:

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        ListNode* slow = head;//慢指针,从头开始
        ListNode* fast = head;//快指针,从头开始
        while (fast!=nullptr) {
            slow=slow->next;//每次移动一格
            if (fast->next==nullptr) {//判断环形链表
                return nullptr;
            }
            fast=fast->next->next;//每次移动两格
            if (slow==fast) {//快慢指针相遇
                ListNode* rot=head;//新建节点
                while (rot!=slow) {//当没相遇时
                    rot=rot->next;
                    slow=slow->next;
                }
                return rot;//相遇在入环节点
            }
        }
        return nullptr;
    }
};


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值