「这是我参与2022首次更文挑战的第41天,活动详情查看:2022首次更文挑战」。
本节介绍的是LeetCode 141和LeetCode 142两题环形链表的解题方法,实际上两题解题思路一致,只不过第142题相比于141题新增了一个返回入环节点。
题目:给定一个链表,判断该链表是否存在环,如果存在则返回入环点,否则返回null。
解题思路
判断链表是否存在环,常规的思路很简单,使用一个容器将环中所有节点依次保存下来,每次保存的时候判断当前容器是否存在此元素,如果存在则表明链表必定有环,如果链表可以访问到null,则表明链表必定没有环,这个数据结构在Java中使用的是HashSet,可得代码如下:
public boolean hasCycle(ListNode head) {
HashSet<ListNode> set = new HashSet<>();
while(head!=null){
if(set.contains(head)){
return true;
}
set.add(head);
head = head.next;
}
return false;
}
上述代码耗时4ms,时间复杂度和空间复杂度都是。下面介绍一种空间复杂度为的方法。
快慢指针
快慢指针指的是初始时存在两个指针指向同一个指针,一个指针为fast,另一个为slow,fast指针每次走两步,slow指针每次走一步。
我们假设当前链表不存在环,则fast指针必然比slow指针先到null指针,此时可以通过判断fast是否为空来判断链表是否存在环。
如果当前链表存在环,则fast指针和slow指针必然会相遇。且相遇时slow指针没有走完一圈。
- 为什么会相遇?如果存在环,则fast指针必然比
slow指针先入环,当slow到达入环点,此时fast开始追赶slow,假设当前fast距离slow为n,则fast走两步,距离变为n-2,slow走一步,距离变为n-1,依次循环,距离必然会变为0。 - 为什么相遇时
slow没有走完一圈?假设当前链表不存在环外部分(即链表是一个首尾相连的环),此时slow和fast相遇在slow刚好走完一圈。如果存在环外部分,则fast相比slow先入环,此时两个指针相遇slow必然没有到达一圈。
则可以得到如下代码:
public boolean hasCycle(ListNode head) {
if(head==null||head.next==null) return false;
ListNode fast = head;
ListNode slow = head;
while(fast!=null){
if(fast.next!=null){
fast = fast.next.next;
}else{
return false;
}
slow = slow.next;
if(fast == slow){
return true;
}
}
return false;
}
上述代码时间复杂度为,空间复杂度为。解决了链表是否存在环,则第142题就简单了。
当fast和slow相遇后,假设相遇节点即为slow,从头节点cur开始和slow同时移动节点,当cur和slow相遇的时候的节点即为入环节点。
具体推导过程看这篇博客: 深入理解快慢指针算法 -- 链表环路检测 - 知乎 (zhihu.com)
可得代码如下:
public ListNode detectCycle(ListNode head) {
if(head==null||head.next==null) return null;
ListNode fast = head;
ListNode slow = head;
while(fast!=null){
if(fast.next!=null){
fast = fast.next.next;
}else{
return null;
}
slow = slow.next;
if(fast == slow){
ListNode cur = head;
while(cur!=slow){
cur = cur.next;
slow = slow.next;
}
return cur;
}
}
return null;
}
时间复杂度为,空间复杂度为。