141. 环形链表
给你一个链表的头节点 head
,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next
指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos
不作为参数进行传递 。仅仅是为了标识链表的实际情况。
如果链表中存在环 ,则返回 true
。 否则,返回 false
。
示例 1:
输入:head = [3,2,0,-4], pos = 1 输出:true 解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
输入:head = [1,2], pos = 0 输出:true 解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:
输入:head = [1], pos = -1 输出:false 解释:链表中没有环。
提示:
- 链表中节点的数目范围是
[0, 104]
-105 <= Node.val <= 105
pos
为-1
或者链表中的一个 有效索引 。
进阶:你能用 O(1)
(即,常量)内存解决此问题吗?
思路
快慢指针,俩指针从头节点出发,一个快指针每次走两步,一个慢指针每次走一步,如果链表存在环路,那么快指针会追上慢指针
/** * Definition for singly-linked list. * class ListNode { * int val; * ListNode next; * ListNode(int x) { * val = x; * next = null; * } * } */ public class Solution { public boolean hasCycle(ListNode head) { if(head == null || head.next == null || head.next.next == null) return false; ListNode slow = head, fast = head; while(fast != null && fast.next!=null){ slow = slow.next; fast = fast.next.next; if(slow == fast) return true; } return false; } }
证明快慢指针算法(也被称为弗洛伊德的龟兔赛跑算法)的正确性,可以通过数学归纳法来完成。下面是证明的逻辑:
-
定义:假设链表由非环部分和环部分组成。设非环部分的长度为 𝑎a 个节点,环的长度为 𝑏b 个节点。
-
快指针进入环:当快指针(
fast
)进入环时,慢指针(slow
)可能还在非环部分,或者已经在环内。 -
快慢指针相遇:当快指针在环内移动时,慢指针也在环内移动,但由于快指针的速度是慢指针的两倍,它们最终会在环内的某个点相遇。
-
证明相遇:假设在快指针进入环之前,快慢指针已经移动了 𝑘k 步(𝑘<𝑎k<a)。此时,快指针开始在环内移动,而慢指针继续在非环部分移动直到它也进入环。当慢指针进入环时,快指针已经在环内移动了 𝑘k 步。
-
环内相遇:在环内,快指针每走 𝑏b 步就会回到起点,而慢指针需要走 2𝑏2b 步。因此,每当快指针绕环一周,慢指针只绕了半周。这意味着,每过 𝑏b 步,快指针就会相对于慢指针多走 𝑏b 步。由于快指针和慢指针之间的初始距离是 𝑘k 步,经过 𝑘𝑏bk 轮之后,快指针将追上慢指针。
-
特殊情况:如果链表只有一个节点,或者没有节点,快慢指针会在第一步就相遇,这符合我们的预期。
-
没有环的情况:如果链表中没有环,快指针会在到达链表末尾时停止,而慢指针会在快指针之前停止,因此它们永远不会相遇。