题目介绍
力扣142题:leetcode-cn.com/problems/li…
方法一:哈希表
一个非常直观的思路是:我们遍历链表中的每个节点,并将它记录下来;一旦遇到了此前遍历过的节点,就可以判定链表中存在环。借助哈希表可以很方便地实现。
代码如下:
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode detectCycle(ListNode head) {
if(head == null || head.next == null) {
return null;
}
Map<ListNode,Integer> dataMap = new HashMap<>();
ListNode p = head;
for(int i = 0; p != null; p = p.next) {
//如果dataMap中已经存在了该节点,则说明这个节点就是环入口的节点,因为是依次遍历的
if(dataMap.containsKey(p)) {
return p;
}else {
//否则将该节点存入到dataMap中
dataMap.put(p,i);
}
}
return null;
}
}
方法二:快慢指针
我们使用两个指针, fast 与 slow。它们起始都位于链表的头部。随后, slow 指针每次向后移动一个位置,而 fast 指针向后移动两个位置。如果链表中存在环,则 fast 指针最终将再次与 slow 指针在环中相遇。
如下图可以看到,当快慢指针相遇时,让其中任一个指针指向头节点,然后让它俩以相同速度前进,再次相遇时所在的节点位置就是环开始的位置。这是为什么呢?
第一次相遇时,假设慢指针slow走了k步,那么快指针fast一定走了2k步:
fast一定比slow多走了k步,这多走的k步其实就是fast指针在环里转圈圈,所以k的值就是环长度的「整数倍」。
说句题外话,之前还有读者争论为什么是环长度整数倍,我举个简单的例子你就明白了,我们想一想极端情况,假设环长度就是 1,如下图:
那么fast肯定早早就进环里转圈圈了,而且肯定会转好多圈,这不就是环长度的整数倍嘛。
言归正传,设相遇点距环的起点的距离为m,那么环的起点距头结点head的距离为k - m,也就是说如果从head前进k - m步就能到达环起点。
巧的是,如果从相遇点继续前进k - m步,也恰好到达环起点。你甭管fast在环里到底转了几圈,反正走k步可以到相遇点,那走k - m步一定就是走到环起点了:
代码如下:
public class Solution {
public ListNode detectCycle(ListNode head) {
if (head == null) {
return null;
}
ListNode fast, slow;
fast = slow = head;
while (fast != null && fast.next != null) {
//快指针每次走两步
fast = fast.next.next;
//慢指针每次走一步
slow = slow.next;
//相遇点,相遇时退出while循环
if (fast == slow) {
break;
}
}
//从头节点出发以及从相遇点出发,两者肯定会在环入口点相遇
slow = head;
while (slow != fast) {
fast = fast.next;
slow = slow.next;
}
return slow;
}
}
复杂度分析
-
时间复杂度:O(N),其中 N 为链表中节点的数目。在最初判断快慢指针是否相遇时,slow 指针走过的距离不会超过链表的总长度;随后寻找入环点时,走过的距离也不会超过链表的总长度。因此,总的执行时间为 O(N)+O(N)=O(N).
-
空间复杂度:O(1)。我们只使用了 slow,fast两个指针。