问题描述
解题思路
题目的意思就是判断输入的链表是否有环的存在,如果有则输出环形的起点节点。
这道题可以分为两步来解决:
一、判断是否存在环
二、找出环的起点节点是哪一个
一、判断是否存在环
这里我们可以使用快慢指针进行判断。为什么要选择快慢指针呢?
假设慢指针走一步,快指针走两步(因此快指针是慢指针的速度的两倍):
因为如果不存在环的话,那么快指针一定会走到链表末尾,最后为空指针,这时链表就不存在环了。
而如果链表存在环,那么快指针一定是比慢指针先进入环中,而慢指针会慢一些时间再进入环中。那么在某一个时间点两个指针都已经在环内了,这个时候这两个指针一定会在环内相遇,为什么?不妨我们可以把环看成一个跑道,因为快指针是慢指针速度的两倍,那么在一定时间内,快指针肯定会追上慢指针(这个高中物理的追及问题不必多说),代码实现如下(这也是力扣141 环形链表的解题代码):
public boolean hasCycle(ListNode head) {
/**
快慢指针,如果存在环,那么快慢指针一定会在环内相遇
*/
ListNode slow = head,fast = head;
while(fast != null && fast.next != null){
slow = slow.next;
fast = fast.next.next;
if(slow == fast){
return true;//两个指针相遇,存在环
}
}
return false;//快指针或其下一个节点为空,不存在环
}
二、找出环的起点节点是哪一个
现在再来说环的起点节点是哪一个。刚才在问题一我们说到,快指针是慢指针的速度的两倍,同时如果存在环,那么快指针一定会在环内追上慢指针,那么在相遇的这个时间点,快慢指针走的时间是相同的,因为快指针的速度是满指针的两倍,那么快指针走的路程就是慢指针的路程的两倍。
如下图,假设s代表慢指针,f代表快指针,两种在y点相遇,同时慢指针从起点到相遇点y走的路程设为x,那么快指针走的路程就是2x,因为快指针先一步走入环中,假设快指针在环内走了n圈后相遇(快指针不一定只走了一圈,取决于环的长度和慢指针进入环内的时间,n=1,2,3…),设环的长度为z,所以有n×z + x = 2×x,即n×z=x
(重点)假设相遇点距离环的起点的路程为m,那么现在慢指针从头开始走,走x-m的路程的时间是等于快指针在相遇点开始走(速度降为一半,即与慢指针速度一样),走(n-1)×z+(z-m)的时间(也就是走n-1圈加上相遇点顺时针走向环起点的时间)是一样的。
为什么?结合上面的式子,我们知道,x = n×z 两边同时减去m,就有x-m=n×z-m,亦即x-m=(n-1)×z+(z-m),现在路程一样,快指针速度变为原来的一半,那么现在两个指针所花费的时间就一样啦,因为所走的路程一样,时间跟速度也一样,所以他们刚好会在环的起点相遇啦。
总代码如下:
public ListNode detectCycle(ListNode head) {
ListNode slow = head,fast = head;
while(fast != null && fast.next != null){
slow = slow.next;
fast = fast.next.next;
if(slow == fast){
//存在环
break;
}
}
if(fast == null || fast.next == null){
return null;//不存在环
}
slow = head;//慢指针从头开始
while(slow != fast){
slow = slow.next;
fast = fast.next;//快指针从相遇点开始,速度变为原来的一半
}
return slow;
}
总结
可能说起来有点啰嗦,但是为了解释清楚还是想说的详细一点。这道题的关键在于理解快指针的速度是慢指针的两倍以及路程和相遇问题,借助这些概念应该会更好的解决该题吧。