链表中的环的入口节点
如果一个链表中包含换,那么应该如何找出环的入口节点?
LeetCode题目:给定一个链表,若其中包含环,请找出该链表的环的入口节点,否则,输出null。
如上图,环的入口节点为3
思路
-
野路子:
- 如果想要实现循环链表,那么必定在单链表的最后重新添加一个原有的节点,那么可以借助set结构对节点进行遍历去重,得到重复的节点就是为环的入口节点
真是平平无奇小天才
- 如果想要实现循环链表,那么必定在单链表的最后重新添加一个原有的节点,那么可以借助set结构对节点进行遍历去重,得到重复的节点就是为环的入口节点
-
正式解题:
借助上题思路(厚积薄发打卡Day103:链表(一)<删除倒数第k个节点>)可以借助快慢双指针的方法来解答,先上结论:
- 假设单链表中存在快慢指针,(如快:node.next.next,慢:node.next),那么两指针一定会在环内的某个点相遇
- 当两指针中间相隔环的个数的节点时,同时以相同速度遍历,那么两指针会在换的入口节点相遇
- 假设快指针为2s,慢指针为s的速度向后遍历,那么两指针相差的节点数为环的个数的倍数
-
证明:
-
用反证法可以秒结论1,假设慢指针追不上快指针,则说明单链表无限长,或者无环
-
第二个可以留给读者去证明(
绝对不是因为我懒) -
第三个有点绕,直接上图:
-
图片:
-
说明:s为慢指针,q为快指针,k为慢指针单位之间走的路程,l为环的长度,k具体得看节点与节点之间的路程,即“->”数量,n则指快指针在环内转圈圈的数量
- 上图是k = 4的情况
-
-
结合理论2,同时从外部观察n的情况,其在链表中的绝对位置是没有改变的,可以看作一直在环内循环等慢指针:
- 可以在开头结合重新设两个指针:一个在头节点,一个在相遇点
- 同时同速出发遍历,即可在入环节点相遇
-
-
解题步骤:
- 用快慢指针找出相遇点
- 安排两个指针,分别从头节点和相遇点同向同速出发
- 最后会在环的入口处相遇
实现
-
野路子(HashSet)
/** * 使用hashset来判断 */ public class GetLoopNode01 { public static ListNode entryNodeOfLoop(ListNode head) { HashSet<ListNode> hs = new HashSet<>(); while (head != null) { //如果包含了,那么这个就是入口结点 if (!hs.add(head)) { return head; } head = head.next; } return null; } public static void main(String[] args) { ListNode node1 = new ListNode(1); ListNode node2 = new ListNode(2); ListNode node3 = new ListNode(3); ListNode node4 = new ListNode(4); ListNode node5 = new ListNode(5); ListNode node6 = new ListNode(6); node1.next = node2; node2.next = node3; node3.next = node4; node4.next = node5; node5.next = node6; node6.next = node3; System.out.println(entryNodeOfLoop(node1));//3 } }
-
正式:
public class GetLoopNode02 { //判断是否成环,如果成环则返回相遇的节点 public static ListNode entryNodeOfLoop0(ListNode head) { if (head == null || head.next == null) { return null; } ListNode slow = head.next; ListNode fast = slow.next; while (slow != null && fast != null) { if (slow == fast) { return slow; } slow = slow.next; fast = fast.next; if (fast!= null){ fast = fast.next; } } return null; } public static ListNode entryNodeOfLoop(ListNode head) { ListNode inLoop = entryNodeOfLoop0(head); if (inLoop == null) { return null; } //头节点指针和相遇点指针,同向同速遍历 ListNode node = head; while (node != inLoop) { node = node.next; inLoop = inLoop.next; } //相遇则返回入环节点 return node; } public static void main(String[] args) { ListNode node1 = new ListNode(1); ListNode node2 = new ListNode(2); ListNode node3 = new ListNode(3); ListNode node4 = new ListNode(4); ListNode node5 = new ListNode(5); ListNode node6 = new ListNode(6); node1.next = node2; node2.next = node3; node3.next = node4; node4.next = node5; node5.next = node6; node6.next = node3; System.out.println(entryNodeOfLoop(node1));//3 } }