【双指针】力扣142:环形链表Ⅱ
给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改链表。
示例:
输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。
方法1:哈希表
一个非常直观的思路是:遍历链表中的每个节点,并将它记录下来;一旦遇到了此前遍历过的节点,就可以判定链表中存在环。借助哈希表可以很方便地实现。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def detectCycle(self, head: ListNode) -> ListNode:
# Hashtable = set()
Hashtable = {}
while head: # head == None 时停止
# 判断是否已经Hash中
# 如果存在,说明该点被访问过,直接返回该点位置
# if head in Hashtable:
if Hashtable.get(head):
return head
# 如果不存在,说明没有被访问过,添加到Hash中
else:
# Hashtable.add(head)
Hashtable[head] = True
head = head.next
return None
时间复杂度:O(N),其中 N 为链表中节点的数目。需要访问链表中的每一个节点。
空间复杂度:O(N)。需要将链表中的每个节点都保存在哈希表当中。
方法2:快慢指针
对于链表找环路的问题,有一个通用的解法——快慢指针(Floyd 判圈法) 。
思路简述:
先定义快慢指针 fast、slow,同时指向链表头部,然后 fast 指针每次移动两步,而 slow 指针每次移动一步。如果有环,那么两指针会在环中相遇。
在这里,从链表头部到入环点的长度为 x,环中整圈长度为 y,而 a 则是入环点到两指针相遇点之间的长度。也就是说,slow 指针经入环点进入之后,再走长度 a 的距离可与 fast 指针相遇,此时 slow 指针走过的距离为 x + a。假设此时 fast 在环中已经先走了 n 圈,再走了 a 的距离与 slow 指针相遇,那么此时 fast 指针走过的距离为 x + ny + a(其中 n 未知)。
因为 fast 指针每次移动两步,slow 指针每次移动一步。那么 fast 指针走过的距离始终是 slow 指针走过距离的两倍。结合上面的式子,可得:2 * (x+a) = x + n * y + a。
对上面的式子进行化简,可得 x = n * y - a,这里可能看不出什么,将式子再变化一下得:x = (n-1) * y + y - a。
由图可知,y-a 就是两指针相遇点到入环点的距离。y 是环一圈的长度,(n-1) * y 表示走了 n-1 圈。也就说指针走了 n-1 圈后,再走 y-a 的距离,可以回到入环点,而此时走过的距离为 x。前面也说过 x 就是链表头部到入环点的长度。
那么此时再定义一个指针 extra 指向链表头部,当 extra 到达入环点时,走过的距离为 x,而环中的 slow 指针也会到达入环点,此时两指针相遇。
思路详述:
- 双指针第一次相遇: 设两指针 fast,slow 指向链表头部 head,fast 每轮走 2 步,slow 每轮走 1 步;
- 第一种结果: fast 指针走过链表末端,说明链表无环,直接返回 null;
- tips: 若有环,两指针一定会相遇。因为 fast 相对于 slow 是一次移动一个节点,每走 1 轮,fast 与 slow 的间距 +1,fast 终会追上 slow;
- 第二种结果: 当 fast == slow 时, 两指针在环中第一次相遇。此时 fast 与 slow 走过的步数关系:
- 设链表共有 x+y 个节点,其中 链表头部到链表入口 有 x 个节点(不计链表入口节点), 链表环有 y 个节点(这里需要注意,x 和 y 是未知数);设两指针分别走了 f, s 步,则有:
- fast 走的步数是 slow 步数的 2 倍,即 f = 2s;
- fast 比 slow多走了 n 个环的长度,即 f = s + n * y;( 双指针都走过 x 步,然后在环内绕圈直到重合,重合时 fast 比 slow 多走 环的长度整数倍);
- 以上两式相减得:f = 2ny,s = ny,即 fast 和 slow 指针分别走了 2n,n 个 环的周长 (注意:n 是未知数,不同链表的情况不同)。
- 设链表共有 x+y 个节点,其中 链表头部到链表入口 有 x 个节点(不计链表入口节点), 链表环有 y 个节点(这里需要注意,x 和 y 是未知数);设两指针分别走了 f, s 步,则有:
- 目前情况分析:
- 如果让指针从链表头部一直向前走并统计步数,那么所有 走到链表入口节点时的步数 是 x+ny(先走 x 步到入口节点,之后每绕 1 圈环(y 步)都会再次到入口节点)。
- 而目前,slow 指针走过的步数为 ny 步。因此,我们只要想办法让 slow 再走 x 步停下来,就可以到环的入口。
- 但是我们不知道 x 的值,该怎么办?依然是使用双指针法
- 构建一个指针,此指针需要有以下性质:此指针和slow 一起向前走 x 步后,两者在入口节点重合。
- 从哪里走到入口节点需要 x 步?答案是链表头部head。
- 双指针第二次相遇:
- slow指针位置不变 ,将fast指针重新指向链表头部节点;slow 和 fast 同时每轮向前走 1 步;
- tips:此时 f=0,s=ny ;
- 当 fast 指针走到 f = x 步时,slow 指针走到步s=x+ny,此时两指针重合,并同时指向链表环入口 。
- 返回slow指针指向的节点。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def detectCycle(self, head: ListNode) -> ListNode:
# 定义快慢指针指向链表头部
slow, fast = head, head
while True:
# if not fast and not fast.next:
if not (fast and fast.next):
return None
fast, slow = fast.next.next, slow.next
if slow == fast:
break
'''
# 定义额外的指针 extra 指向链表头部,extra 与 slow 相遇会到达入环点
extra = head
while extra != slow:
extra = extra.next
slow = slow.next
return extra
'''
fast = head
while fast != slow:
fast, slow = fast.next, slow.next
return fast
另一种代码(4个链表,其实和上面代码备注的一样,是三个链表):
class Solution:
def detectCycle(self, head: ListNode) -> ListNode:
slow, fast = head, head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
# 如果相遇
if slow == fast:
p = head
q = slow
while p!=q:
p = p.next
q = q.next
return p # 也可以return q
return None
时间复杂度:O(N),其中 N 为链表中节点的数目。在最初判断快慢指针是否相遇时,slow 指针走过的距离不会超过链表的总长度;随后寻找入环点时,走过的距离也不会超过链表的总长度。因此,总的执行时间为 O(N)+O(N)=O(N)。
空间复杂度:O(1)。只使用了slow, fast (, extra) 几个指针。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!