力扣原题:
先贴代码:
public class Solution142 {
public ListNode detectCycle(ListNode head) {
//新建fast节点和slow节点
ListNode fast = head;
ListNode slow = head;
//循环判断fast节点和fast节点的后继节点是否为空,若为空则无环,退出循环返回null
while (fast != null && fast.next != null){
//fast走两步,slow走一步
fast = fast.next.next;
slow = slow.next;
//判断是否相遇
if (fast == slow){
//相遇后将slow移至开头,再以相同速度前进,直至再次相遇
slow = head;
while (fast != slow){
fast = fast.next;
slow = slow.next;
}
//返回此时相遇的节点,即为入环的第一个节点
return slow;
}
}
return null;
}
}
思路解析:
题目中要求我们返回开始入环的第一个节点,首先我们拿到这个题目的时候第一时间想到的肯定是遍历链表,看看有哪些next域是重复的,如果链表中有两个节点的next域都指向同一个节点,那么这个节点肯定是开始入环的第一个节点。但是如果这么做的话我们就需要将每一个节点的next域保存起来,再来慢慢进行对比,这样的话空间复杂度肯定就不止o(1)了,那么有没有什么办法可以只用o(1)的空间来解决问题呢?
这里我们可以使用快慢指针的方法,创建一个快指针和一个慢指针,快指针每次走两步,慢指针每次走一步,如果链表中是有环的,那么快慢指针迟早会相遇,若快指针无法和慢指针相遇则说明链表中无环;那么又如何找出开始入环的第一个节点呢?
如下图演示,快指针fast和慢指针slow一开始都在头节点的位置,称其为起始点,现在开始快指针和慢指针同时开始往后走,快指针往后走两步,慢指针往后走一步,这样快指针和慢指针肯定会在环中相遇而不是在非环链表部分中相遇,
可以将上图简化为下图,设头节点的位置为起始点,开始入环的位置为入环点,二者在环中相遇的节点为相遇点,则可得到:
在上文中我们得知,快指针每次走两步,慢指针每次走一步,所以快指针速度是慢指针的二倍,在相同情况下,如果二者要相遇(假设在相遇前快指针已经在环中跑了n圈),那么就必然满足以下式子:
即快指针经过n圈到达相遇点的路程是慢指针到达相遇点路程的二倍,经过以下化简可得:
x = (n-1)c+y ,即起始点到入环点的距离是相遇点到入环点的距离加上(n-1)圈,当n为1的时候,x就等于y;所以我们可以得出结论:不管快指针走了多少圈,到最后都是和慢指针在相遇点停下, 再将慢指针移至头节点,然后二者以相同速度往前走,就会再次在入环点相遇。那么我们就找到了开始入环的节点。
代码解析:
1、新建fast节点和slow节点,同时指向head节点
2、创建while循环,因为fast始终走在slow前面,所以由它来判断此链表是否有环,若检测到了null,则说明链表无环,直接退出while循环,返回null。
3、进入循环后fast每次走两步,slow每次走一步,直至二者相遇
4、相遇后再将slow移至头节点,以相同速度前进,相遇后的节点即为开始入环的节点!