大家好!我是曾续缘😛
今天是《LeetCode 热题 100》系列
发车第 26 天
链表第 5 题
❤️点赞 👍 收藏 ⭐再看,养成习惯
环形链表 II 给定一个链表的头节点
head
,返回链表开始入环的第一个节点。 如果链表无环,则返回null
。如果链表中有某个节点,可以通过连续跟踪
next
指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。如果pos
是-1
,则在该链表中没有环。注意:pos
不作为参数进行传递,仅仅是为了标识链表的实际情况。不允许修改 链表。
示例 1:
输入:head = [3,2,0,-4], pos = 1 输出:返回索引为 1 的链表节点 解释:链表中有一个环,其尾部连接到第二个节点。示例 2:
输入:head = [1,2], pos = 0 输出:返回索引为 0 的链表节点 解释:链表中有一个环,其尾部连接到第一个节点。示例 3:
输入:head = [1], pos = -1 输出:返回 null 解释:链表中没有环。提示:
- 链表中节点的数目范围在范围
[0, 104]
内-105 <= Node.val <= 105
pos
的值为-1
或者链表中的一个有效索引进阶:你是否可以使用
难度:💖💖O(1)
空间解决此题?
解题方法
寻找链表中环的入口节点
这道题需要找到链表中环的入口节点,我们同样可以使用快慢指针的算法来解决。
假设从链表头部到环的入口有 a a a 个节点,环中有 b b b 个节点。一个指针要走到入口处,其走过的步数 k k k 满足 k = a + n × b k = a + n \times b k=a+n×b 的数学关系。这意味着要么直接走 a a a 步到达入口,要么先走到入口然后绕环 n n n 圈回到入口。
通过快慢指针可以检测链表是否存在环。当快慢指针相遇时,假设快指针走过的步数为 f f f,慢指针走过的步数为 s s s,则有以下关系:
- 快指针速度是慢指针的两倍,即 f = 2 × s f = 2 \times s f=2×s
- 快慢指针在环内相遇,快指针比慢指针多走了 n n n 圈,因此 f = s + n × b f = s + n \times b f=s+n×b
通过上述方程组,我们得到重要的信息: s = n × b s = n \times b s=n×b,这意味着什么呢?
当快慢指针相遇时,慢指针走过的步数是环节点数的整数倍!
为了使慢指针到达环的入口,即满足 k = a + n × b k = a + n \times b k=a+n×b,只需让慢指针再走 a a a 步即可,或者继续多走几圈再返回。
然而, a a a 是未知数,我们并不知道具体需要走多少步,该怎么办呢?
a a a 表示链表头部到链表入口的节点数,我们可以将快指针置于链表头部,与慢指针速度相同。当它们再次相遇时,它们之间的距离差就是链表头部到链表入口的节点数,因此我们也就获得了 a a a,慢指针正好满足入口公式的条件。
过程如下:
- 首先设定两个指针 slow 和 fast,初始化它们都指向头节点。
- 使用一个 do…while 循环,循环内部判断快指针和快指针的下一个节点是否为空,如果有一个为空,则说明链表无环,直接返回 null。
- 在循环内部,快指针每次向前移动两步,慢指针每次向前移动一步,直到它们相遇。
- 当快指针和慢指针相遇时,将快指针重新指向头节点,并将快指针和慢指针都以每次一步的速度向前移动。
- 当快指针和慢指针再次相遇时,相遇的节点即为环的入口节点。
Code
/*** Definition for singly-linked list.* struct ListNode {* int val;* ListNode *next;* ListNode(int x) : val(x), next(NULL) {}* };*/
public class Solution {public ListNode detectCycle(ListNode head) {if (head == null) {return null;}ListNode slow = head, fast = head;do {if (fast == null || fast.next == null) {return null;}fast = fast.next.next;slow = slow.next;} while (fast != slow);fast = head;while (fast != slow) {fast = fast.next;slow = slow.next;}return fast;}
}