链表中环的入口节点
1、参考资料
https://www.nowcoder.com/practice/6e630519bf86480296d0f1c868d425ad
https://blog.nowcoder.net/n/c42a259697014745b1688f9c6795d206
2、题目要求
对于一个给定的链表,返回环的入口节点,如果没有环,返回 null
拓展:你能给出不利用额外空间的解法么?
3、代码思路
方法一:使用 HashSet
记录链表节点地址
怎么才算是环的入口?遍历链表,重复出现的第一个节点便是环的入口
遍历链表中每个节点,每遍历一个节点时,看下 Set
集合里面有没有该节点,如果有,则证明这是环的入口,将该节点返回;如果没有,则将其添加至 Set
集合中
如果遍历完整个链表,都没有找到重复的节点,则证明该链表没有环,return null;
方法二:快慢指针法
题目【判断链表是否有环】中使用到了快慢指针法,但只能保证 slow
和 fast
指针在环中的某一个节点相遇,那么如何通过快慢指针找到入口节点呢?
假设 fast
指针和 slow
指针在 Z
点相遇,此时 fast
指针走过的距离为 a+b+n*(b+c)
(其实 n
为快指针在环中走的圈数),slow
指针走过的距离为 a+b
,因为 fast
指针速度为 slow
指针速度的两倍,所以 a+b+n*(b+c) = 2*(a+b)
,可得出 a=(n-1)*(b+c)+c
,其中 (b+c)
为链表环形部分的长度
上式说明什么?如果 fast
和 slow
相遇时,我们让 slow
回到起点 X
处,再让 slow
和 fast
以相同的速度(每次走一步)往后移动,当 slow
向后移动 a 步时,fast
走了 (n-1)
圈外加向后移了 c
步,所以 slow
和 fast
会相遇在 Y
点(环形入口点)
4、代码实现
方法一:使用 HashSet
记录链表节点地址
public class Solution {
public ListNode detectCycle(ListNode head) {
Set<ListNode> set = new HashSet<>();
ListNode pointer = head;
while (pointer !=null){
if(set.contains(pointer)){
return pointer;
}
set.add(pointer);
pointer = pointer.next;
}
return null;
}
}
class ListNode {
int val;
ListNode next;
ListNode(int x) {
val = x;
next = null;
}
}
方法二:快慢指针法
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode slow = head; // 慢指针
ListNode fast = head; // 快指针
// 快指针每次需要向后移动两步,所以需要判断 fast.next 是否为 null
while (fast != null && fast.next != null) {
slow = slow.next; // 慢指针走一步
fast = fast.next.next; // 快指针走两步
// 当快慢指针相等时,证明链表有环
if (slow == fast) {
slow = head; // 让 slow 重新回到首节点
// 两个指针以相同的速度向后走,相遇在入口节点
while (slow != fast) {
slow = slow.next;
fast = fast.next;
}
return slow;
}
}
return null;
}
}
class ListNode {
int val;
ListNode next;
ListNode(int x) {
val = x;
next = null;
}
}