题目描述
给一个长度为n链表,若其中包含环,请找出该链表的环的入口结点,否则,返回null.
如下图所示, 两段链表{ 1 , 2 } , { 3 , 4 , 5 } , 头结点是1, 环节点是3,{ 3 , 4 , 5 } 成环.
我们的目标就是在给定的一个链表中找出环节点, 即节点3.
题目考察内容
- 链表的快慢指针:了解快慢指针的基本思想。
- 判断链表是否有环:实现一个函数判断链表是否有环。
- 找到环的入口节点:如果链表有环,实现一个函数找到环的入口节点。
解题思路
什么是链表的快慢指针?
快慢指针判断链表是否有环是一种非常巧妙的思想,其基本思路是使用两个指针,一个慢指针每次前进一步,一个快指针每次前进两步。如果链表中存在环,那么快指针最终会追上慢指针;如果没有环,快指针会先到达链表尾部。
基于上述快慢指针思想, 如何判断链表是否有环?
定义慢指针和快指针:将慢指针
slow
指向链表头部,将快指针fast
指向链表头部的下一个节点。为了避免一开始就相遇,快指针fast
比慢指针slow
先前进一步。判断条件:在循环中,我们首先判断
slow
和fast
是否相等,如果相等,说明快慢指针相遇,链表中有环,返回true
。快慢指针移动:在循环中,每次慢指针
slow
前进一步,快指针fast
前进两步。这样,如果链表中存在环,快指针会一直在环内转动,最终追上慢指针;如果链表中不存在环,快指针会率先到达链表尾部。循环条件:循环条件是
slow != fast
,如果快慢指针相遇了,循环结束,函数返回true
;如果快指针到达链表尾部,即fast == null || fast.next == null
,说明链表中没有环,循环结束,函数返回false
。
public boolean hasCycle(ListNode head) {
//对头结点就行校验
if (head == null || head.next == null) {
return false;
}
ListNode slow = head; // 慢指针
ListNode fast = head.next; // 快指针
// 判断链表是否有环
while (slow != fast) {
if (fast == null || fast.next == null) {
return false; // 如果快指针到达链表尾部,说明没有环
}
slow = slow.next; // 慢指针前进一步
fast = fast.next.next; // 快指针前进两步
}
return true; // 快慢指针相遇,说明链表有环
}
如何找到环的入口结点
根据上述图示,A代表头结点,B代表环节点,C代表相遇点C, 环中一圈长度我们用L标识.
基于判断是否链表是否有环的思想, 这一次我们让快慢指针同时从头结点A出发,每一次慢指针移动一步,快指针移动2步,最终第一次相遇在相遇点C.
在相遇点C, 慢指针与快指针相遇, 相遇的时候, 可能快指针比慢指针跑了n圈环中圈数。
- 慢指针走过的距离: x + y- 快指针走过的距离: x + y + n * L
根据快慢指针距离的关系, 基于“每一次慢指针移动一步,快指针移动2步”, 快指针走过的距离一定慢指针的两倍。
距离推导公式:
x + y + n* L = 2( x + y) --> x = (n−1)⋅L+(L−Y)
这个是什么意思呢?将上述推导出来的转变成距离描述如下:
链表头到环入口的距离 =(n-1)* 圈数环长度 + 相遇点到环入口的距离(B点到C点)
链表头到环入口的距离可以理解成从头结点开始遍历, 走过链表头到环入口的距离, 这个距离又等于(n-1)* 圈数环长度 + 相遇点到环入口的距离。(n-1)* 圈数环长度 + 相遇点到环入口的距离可以这样理解, 当快慢指针在相遇点C相遇后,如果从快慢指针相遇的节点的位置继续前进, (n-1)* 圈数环长度 表示是完整的一圈, 意味着从相遇点经过多少圈仍然在C的位置,还得加上相遇点到环入口的距离也就是图中相遇点C-环节点B的距离.
结论: 因此到了这里,解答这个题目的思路就明显了,先是定义了快慢指针, 如果快慢指针相遇,表明这个链表有环, 在有环的前提下,从快慢指针相遇的C点继续前进,继续再定义一个指针从头节点A出发,当头节点A走过链表头到环入口的距离, 根据等式表明相遇C点继续前进走过(n-1)* 圈数环长度 + 相遇点到环入口的距离,是不是会相遇?
相遇的这个点就是我们要求的环节点入口!!!
解题java代码
public ListNode EntryNodeOfLoop(ListNode head) {
if (head == null || head.next == null) {
return null;
}
ListNode slow = head;
ListNode fast = head;
// 判断链表是否有环
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
if (slow == fast) {
// 相遇,存在环
ListNode entry = head;
// 找到环的入口节点
while (entry != slow) {
entry = entry.next;
slow = slow.next;
}
return entry;
}
}
return null; // 无环
}