142. 环形链表 II
给定一个链表的头节点 head
,返回链表开始入环的第一个节点。 如果链表无环,则返回 null
。
如果链表中有某个节点,可以通过连续跟踪 next
指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos
是 -1
,则在该链表中没有环。注意:pos
不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改 链表。
示例 1:
输入: head = [3,2,0,-4], pos = 1
输出: 返回索引为 1 的链表节点
解释: 链表中有一个环,其尾部连接到第二个节点。
示例 2:
输入: head = [1,2], pos = 0
输出: 返回索引为 0 的链表节点
解释: 链表中有一个环,其尾部连接到第一个节点。
示例 3:
输入: head = [1], pos = -1
输出: 返回 null
解释: 链表中没有环。
提示:
- 链表中节点的数目范围在范围 [ 0 , 1 0 4 ] [0, 10^{4}] [0,104] 内
- − 1 0 5 < = N o d e . v a l < = 1 0 5 -10^5 <= Node.val <= 10^{5} −105<=Node.val<=105
pos
的值为-1
或者链表中的一个有效索引
进阶: 你是否可以使用 O(1)
空间解决此题?
解法
解法一(哈希表)
思路分析:
- 创建一个哈希表
hashNode
存储链表中的节点 - 对链表进行遍历,让出现重复节点时,即为链表开始入环的第一个节点,将其返回即可
实现代码如下:
class Solution {
public ListNode detectCycle(ListNode head) {
// 创建哈希表
HashMap<ListNode, Integer> hashNode = new HashMap<>();
ListNode cur = head;
while (cur != null) {
if (hashNode.containsKey(cur)) { // 存在节点直接返回
return cur;
}
hashNode.put(cur, 1);
cur = cur.next;
}
return null;
}
}
提交结果如下:
解答成功:
执行耗时:3 ms,击败了18.03% 的Java用户
内存消耗:42.9 MB,击败了14.97% 的Java用户
复杂度分析:
- 时间复杂度:O(n),遍历一遍链表
- 空间复杂度:O(n),需要存储链表的节点
解法二(双指针)
思路分析:
- 使用两个指针
fast
和slow
,起始都位于链表的头部,slow
每次向后移动一个位置,fast
每次向后移动两个位置,若链表中存在环,则fast
指针最终会再次与slow
指针在环中相遇 - 因为
fast
每次比slow
多走一步,有环,则fast
一定会追上slow
- 假设链表长度为
a+b
,即a
指链表头部到链表环入口的长度,b
为环的长度- 且
fast
指针走的步数是slow
走的步数的两倍, f = 2 s f = 2s f=2s - 当
fast
与slow
相遇时,两个指针所走长度关系为, f = s + n b f = s + nb f=s+nb,nb
指的是n
圈环的长度 - 则有
s
=
n
b
s = nb
s=nb,即两个指针
fast
和slow
分别走了2n
、n
个环的周长
- 且
- 且有链表环的入口点为,从链表头部走
a
+
n
b
a+nb
a+nb步到达,
fast
和slow
相遇时时slow
已走过nb
步,即只需再走a
步,slow
指向链表环的入口点 - 此时,让一个指针
ptr
从链表头部开始遍历,且与slow
同时移动,当指针ptr
走过a步时,指向链表环的入口点,恰好此时指针slow
也指向链表环的入口点,即两个指针相遇时,刚好均指向链表环的路口点
实现代码如下:
class Solution {
public ListNode detectCycle(ListNode head) {
ListNode fast = head;
ListNode slow = head;
// 若fast为空则说明不含有环 退出循环
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
if (fast == slow) { // 当fast与slow相遇
ListNode ptr = head;
while (ptr != slow) { // 寻找环入口处节点
slow = slow.next;
ptr = ptr.next;
}
return ptr;
}
}
return null;
}
}
提交结果如下:
解答成功:
执行耗时:0 ms,击败了100.00% 的Java用户
内存消耗:43 MB,击败了10.51% 的Java用户
复杂度分析:
- 时间复杂度:O(n),对链表进行遍历
- 空间复杂度:O(1),使用了常量级变量