【HOT 100】142. 环形链表 II
题目描述
给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意,pos 仅仅是用于标识环的情况,并不会作为参数传递到函数中。
说明:不允许修改给定的链表。
进阶:
你是否可以使用 O(1) 空间解决此题?
方法一:快慢指针
判断一个链表是否有环,可以通过快慢指针实现,快指针每次走两步,慢指针每次走一步,若相遇,则说明有环。
算法思路:
- 定义三个指针,一个环头指针,用于对链表实现一次遍历,获取环头节点;另外两个快慢指针,判断是否存在环。
- 环头指针从链表头开始遍历,每移动一个节点,快慢指针从当前节点出发,判断是否存在环,且记录当前节点是否在环中。
时间复杂度:O(n^2),空间复杂度O(1)
ListNode *detectCycle2(ListNode *head)
{
ListNode *fast = head; // 快指针,每次走两步
ListNode *slow = head; // 慢指针,每次走一步
ListNode *cycle_head = head; // 记录头节点指针,每次快慢指针从头节点开始走
bool pos = false; // 标识:当前头节点是否在环中
bool has_cycle = false; // 标识:是否存在环
while (!pos)
{
// 判断是否存在环
while (slow->next != nullptr && fast->next != nullptr && fast->next->next != nullptr)
{
fast = fast->next->next;
slow = slow->next;
if (fast == cycle_head || slow == cycle_head) // 判断当前头节点是否在环中
pos = true;
if (fast == slow) // 判断是否存在环
{
has_cycle = true;
break;
}
}
if ((has_cycle && pos) || cycle_head->next == nullptr) // 找到存在环的头节点,或者遍历结束
break;
else
{
cycle_head = cycle_head->next;
fast = cycle_head;
slow = cycle_head;
}
}
if (pos)
return cycle_head;
return nullptr;
}
方法二:快慢指针优化
上一个方法中,是对当前链表遍历时,每个节点都要执行:判断是否是环头节点,并且是否存在环。
实际上,不管从那个节点出发,都对环的存在判断没影响,只需要判断一次就好。所以,关键在于定位环头节点。
官方的思路:https://leetcode-cn.com/problems/linked-list-cycle-ii/solution/huan-xing-lian-biao-ii-by-leetcode-solution/
a:链表中环外部分的长度
b: 满指针进入环后,走了 b 的距离与快指针相遇
因此,根据快指针走的距离,有下列等式
a+(n+1)b+nc=2(a+b)⟹a=c+(n−1)(b+c)
而 b+c 等于环的一圈,不影响 a 的位置,因此可以得出a = c
的结论。
也就是说,快慢指针相遇后,慢指针继续走到环头节点的距离,与环外距离的长度一致,那样接下来算法就很好写了。
ListNode *detectCycle(ListNode *head)
{
if (!head)
return nullptr;
ListNode *fast = head;
ListNode *slow = head;
ListNode *cycle_head = head;
// 判断是否存在环
while (slow->next != nullptr && fast->next != nullptr && fast->next->next != nullptr)
{
fast = fast->next->next;
slow = slow->next;
if (fast == slow) // 存在环,跳出
break;
}
// 如果存在环
if (fast->next && slow->next && fast->next->next)
{
while (cycle_head != slow)
{
slow = slow->next;
cycle_head = cycle_head->next;
}
return cycle_head;
}
return nullptr;
}
方法三:hash表
如果不追求O(1)的空间复杂度,可以将遍历过的节点记录在 hash 表中,如果表中已经存在当前结点,说明存在环,且当前结点是环头结点。