本文主要讲解leetcode中141.环形链表和142.环形链表II 基于 HashMap 和 快慢指针两种解法。
一、题目
二、分析
141.环形链表分析
方法一:哈希表
-
思路
我们可以通过检查结点此前是否被访问过来判断链表是否为环形链表。常用的方法是使用哈希表 -
算法
我们遍历所有结点并在哈希表中存储每个结点的引用(或内存地址)。如果当前结点为空结点 null(即已检测到链表尾部的下一个结点),那么我们已经遍历完整个链表,并且该链表不是环形链表。如果当前结点的引用已经存在于哈希表中,那么返回 true(即该链表为环形链表)。 -
优缺点
使用HashMap是最容易想到和常用的方法。但是需要O(N)的空间复杂度。 -
链接:https://leetcode-cn.com/problems/linked-list-cycle/solution/huan-xing-lian-biao-by-leetcode/
方法二:双指针
-
思路
想象一下,两名运动员以不同的速度在环形赛道上跑步会发生什么?
跑的快的运动员一定会追上跑的慢的运动员 -
算法
通过使用具有 不同速度 的快、慢两个指针遍历链表,空间复杂度可以被降低至 O(1)O(1)。慢指针每次移动一步,而快指针每次移动两步。
如果列表中不存在环,最终快指针将会最先到达尾部,此时我们可以返回 false。
现在考虑一个环形链表,把慢指针和快指针想象成两个在环形赛道上跑步的运动员(分别称之为慢跑者与快跑者)。而快跑者最终一定会追上慢跑者。这是为什么呢?考虑下面这种情况(记作情况 A)- 假如快跑者只落后慢跑者一步,在下一次迭代中,它们就会分别跑了一步或两步并相遇。
142.环形链表II分析
方法一:哈希表
思路同141
方法二:双指针
思路和141大致是相同的。但是需要明确3点:
1.本题要求返回入环的第一个节点。
2.快慢指针一次相遇的点并不是入环的第一个节点。
3.当快慢指针第一次相遇时,重新让快指针指向头节点,然后和慢指针一步一步走,再一次相遇的节点就是入环的第一个节点。
推论过程可以参考 :Krahets的题解。强烈推荐看看。
三、题解
- 141 HashMap题解
public boolean hasCycle(ListNode head) { //【方法一】 hash表 遍历连表 检查之前是否访问过这个节点 //创建hash表 HashSet<ListNode> hashSet= new HashSet<ListNode>(); while(head != null){ //是否已经访问过这个节点 if(hashSet.contains(head)){ return true;//存在环 }else{ hashSet.add(head);//没有访问就添加到hash表 } head = head.next; } //循环结束 没有找到 return false; }
- 141 双指针(快慢指针)题解
public boolean hasCycle(ListNode head) { //特例处理 if(head == null || head.next == null) return false; //无环 //快慢指针 ListNode slow = head; ListNode fast = head; // 这里不用fast = head.next 循环的条件不一样 while(fast !=null && fast.next != null){ //注意判断条件 需要判断 fast 和 fast.next 都不为null。 这样fast.next.next 就不会出现空指针 slow = slow.next; fast = fast.next.next; if(slow == fast) return true;// 一定要注意: 相遇的点不一定就是环点! 相遇只能说名有环。 } //fast == null 无环 return false; }
- 142 双指针题解
// 有一个重点 : 当两个指针第一相遇的时候(很有可能不是环点相遇)), 就让fast指向head节点, 然后一步一步的走, slow也继续一步一步走。 再次相遇的点就是环点! public ListNode detectCycle(ListNode head) { //特例处理 if(head == null || head.next == null) return null; ListNode fast = head; ListNode slow = head; while(fast !=null && fast.next != null){ fast = fast.next.next; slow = slow.next; if(slow == fast) break; } //需要判断下跳出循环的条件 if(fast ==null || fast.next == null) return null; //fast指向头部 一步一步走 fast = head; while(fast != slow){ fast = fast.next; slow = slow.next; } //跳出循环 fast = slow = 环点。 return fast; }
四、复杂度
- 哈希表: 时间O(n),空间O(n)
- 双指针: 时间O(n),空间O(1)