本篇主要是在学习链表数据结构中时,操作环形链表时所一步一步执行的操作。
题目来源于力扣142.环形链表Ⅱ。
此题在考察链表的基础操作下,还主要考查了两个知识点:
(1)判断链表是否有环
(2)如果有环,如何找到该环的入口
首先我们回答问题(1):如何判断链表是否存在环?
回顾我们之前的单链表
,假设我们定义了双指针(快慢指针)
,如果链表没有环
,那么由于fast指针
(快指针)走得快
,一旦超过slow指针
(慢指针)那么slow指针
之后将不再
有机会和fast指针相遇
,这是毋庸置疑的,因为是一直沿着直线走下去的。
那么如果链表有环呢?slow指针是不是也永远不可能再和fast指针相遇呢?答案是一定会相遇。我们可以这样定义fast和slow指针,其中fast指针每次移动两个节点
,slow指针每次移动一个节点
,如果这样fast和slow在途中相遇,说明这个链表有环
。
下面解释为什么这样定义,以及这样定义为什么一定会让fast和slow指针在环内相遇:
fast指针每次走两节点,slow指针每次走一节点,那么相当于是fast指针
再以2-1=1节点的速度追赶slow指针
,1节点的速度追赶相当于是单步单步
去接近slow指针
的,所以一定
会在某个时刻
和slow指针相遇
。如果我们这里定义fast
每次走三节点
,那么就不一定会相遇
了,因为此时fast
是以两节点的速度追赶slow
,由于不是单步单步去逼近slow,所以有可能直接跳过slow
。
对于问题(2):如果链表有环,怎样才能找到环的入口呢?
我们可以先假设头节点到环形入口节点的节点数为x
;环形入口节点到fast指针与slow指针相遇节点的节点数为y
;从相遇节点再到环形入口节点的节点数为z
。如下图:
则相遇时:
slow指针
走过节点数
: x+y
fast指针
走过节点数
: x+y+n(y+z)
这里的n
相当于是fast指针
在环内走了n圈
才与slow指针相遇
由于fast指针一次走2节点,slow一次走1节点,那么fast指针走过节点数=2*slow指针走过节点数
由此:
2(x+y)=x+y+n(y+z)
化简一下得到:
x=(n-1)(y+z)+z
这里n一定是大于等于1
的,因为fast至少
要多走一圈
才能与slow相遇
。
通过化简后的公式,我们可以分析出:
当n=1
时,x=z
,说明fast指针在环内走了一圈
后就与slow指针相遇
了;同时,由于x=z,说明从头节点
出发一个指针index1
,从相遇节点
也出发一个指针index2
,两指针每次只走1个节点
,那么当这两个指针相遇
时也就找到了环形入口的节点
;
当n>1
时,x=(n-1)(y+z)+z
,我们仍然可以这样定义两个指针,只不过此时的index2指针
在环内多走了n-1圈
后才与index1指针
在入口节点处相遇
。
由此便可编写代码:
public class Solution {
public ListNode detectCycle(ListNode head) {
//定义快慢指针
ListNode slow = head;
ListNode fast = head;
//由于fast指针走得快,仅对其条件判断即可
//由于fast指针一次走2个节点,在判断当前节点不为空的条件下,还要判断下一节点不为空
while (fast != null && fast.next != null) {
//慢指针1次走1个节点
slow = slow.next;
//快指针1次走2个节点
fast = fast.next.next;
if (slow == fast) {// 说明有环,且快慢指针相遇点为slow/fast
//定义一个从相遇点出发的指针index1
ListNode index1 = fast;
//定义一个从头节点出发的指针index2
ListNode index2 = head;
// 两个指针,从头结点和相遇结点,同时开始走,一次走一步,直到相遇,相遇点即为环入口
while (index1 != index2) {
index1 = index1.next;
index2 = index2.next;
}
//跳出while循环后说明index1=index2,此时相遇,说明环入口即为index1/index2
return index1;
}
}
//链表无环,返回null
return null;
}
}