1.易懂 常规思路

首先这题要我们找链表的环入口结点,最常规易懂的解法就是遍历整个链表结点,然后用哈希表来存储已访问过的结点,最后进行对比。 若该结点已存在哈希表中,则代表该结点是我们要找的环形链表的入口结点;否则把结点添加到哈希表中,继续往下遍历。

2.图解 哈希表解法

这里我们就用题目中给的三个示例来解释一下上面的具体操作👇,首先是第一个示例:

输入:{1,2},{3,4,5}
返回值:3
图片说明
上图表示边遍历链表,边把已访问过的结点存入哈希表中
图片说明
然后到最后一个结点的时候,指针又回到node3 的位置,这就表明这里存在一个环形链表。而node3就是区别这个环形链表的入口结点!!!

下面看第二个示例的哈希表图解👇:
图片说明
同理第三个示例👇:
图片说明

3.核心代码

相信到这里大家应该很明白哈希表解法了,下面是核心代码:

# -*- coding:utf-8 -*-
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None
class Solution:
    def EntryNodeOfLoop(self, pHead):
        #创建个已访问过结点的哈希表
        nodes = set()
        #首先判断链表有无成为环的结点:
        while pHead:                #有结点的情况下:
            if pHead in nodes:      #如果结点存在于哈希表中,就返回结点
                return pHead
            else:
                nodes.add(pHead)     #否则把结点添加到哈希表中
                pHead = pHead.next   #继续遍历下一个结点
        return None                 #无结点就返回None

4. 复杂度分析

时间复杂度:O(n) (因为最糟糕的情况就是遍历了整个链表,n 代表链表中的结点数)

空间复杂度:O(n) (最糟糕的情况是把整个链表的结点插入哈希表中)

图片说明

可以看出此法虽然易懂但不是最优,所以还需优化。

--------------------------------------------我是解法二的分割线----------------------------------------------------

5. 解法二:双指针

解法一的哈希表虽然简单易懂,但是空间复杂度为O(n),这里我们还可以进行优化,使用 双指针方法即可让它变为O(1)的空间复杂度;下面依然是图解示例进行解说。

5.1 思路+图解

  • 首先我们创建快、慢两个指针(fast、slow),然后fast指针一次走2步;slow指针一次走 1步
  • 接着我们需要先判断:是否存在环?如果不存在,则直接返回None;否则fast、slow指针一定会相遇。
  • 当环存在的时候,假设环之前有m个节点,环之内有n个节点;且slow指针已经走了j 步,我们可以发现第一次两指针相遇时,fast指针走过步数为:
    fast = j + i*n ( i 表示fast指针比slow指针多走了 i 个环
    同样根据一开始的fast、slow定义,fast指针的步数也可以这样表达:fast = 2*slow = 2j。 带入上面的等式,可以解出👉j = i*n 的等式。
    • 所以可知fast = 2 * i * n , slow = i * n,这说明了fast指针和slow指针第一次相遇的时候都走过了环的倍数!!! 然后再走 j 步就是环的入口节点
  • 接下来让 fast从链表头部再次出发,而slow 则位置不变;且fast 和slow 都开始以每次走一步的相同频率继续行进。
  • 当fast 走到 j 步的时候,slow 就走了 j + i * n 步,此时 fast和slow两指针重合!这就是我们要找的环入口节点!返回slow,因为它就在入口节点处。

还是看看示例图:
图片说明
👆上图为不存在环的情况;下面是存在环的情况图解👇:
第一次相遇:
图片说明
第二次相遇:
图片说明

5.2 核心代码

# -*- coding:utf-8 -*-
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None
class Solution:
    def EntryNodeOfLoop(self, pHead):
        #创建快、慢两种指针
        fast, slow = pHead, pHead
        while True:
            #第一种结果: fast 指针走过链表末端,说明链表无环,返回 None
            if not fast or not fast.next:
                return None            
            #慢指针一次走一步;快指针一次两步
            slow = slow.next
            fast = fast.next.next
            #如果有环,第一次相遇:
            if slow == fast:
                break
        #第二次相遇,fast回到链表起点位:
        fast = pHead
        #fast、slow两指针不相遇的时候都各走一步:
        while slow != fast:
            fast = fast.next
            slow = slow.next
        #再次相遇即slow所在节点
        return slow 

5.3 复杂度分析

  • 时间复杂度:O(n) (其中n为链表中节点的数目,快慢指针走过的距离都不会超过链表的长度)
  • 空间复杂度:O(1) (双指针使用常数大小的额外空间)