
这是一个经典的链表算法题,通常使用**快慢指针法(Floyd 判圈算法)**来解决。这种方法不需要额外的存储空间,时间复杂度为 O(N)。
算法思路解析
-
判断是否有环:
-
使用两个指针
fast和slow。 -
fast每次走两步,slow每次走一步。 -
如果链表中没有环,
fast最终会遇到nullptr。 -
如果链表中有环,
fast最终会追上slow,两者会在环内相遇。
-
-
寻找环的入口:
-
当
fast和slow相遇时,假设链表头到环入口距离为 x,环入口到相遇点距离为 y,从相遇点再回到环入口距离为 z。 -
相遇时,
slow走了 x + y,fast走了 x + y + n(y + z)(即多走了 n 圈)。 -
因为
fast速度是slow的两倍,所以:2(x + y) = x + y + n(y + z)。 -
化简得:x = (n - 1)(y + z) + z。
-
这个公式的含义是:从头节点出发的指针 和从相遇点出发的指针 ,同时每次走一步,它们最终会在环的入口节点相遇。
-
复杂度分析:
-
时间复杂度:O(N)。在判断是否有环时,快慢指针最多遍历链表一次;寻找入口时,也最多遍历一次。
-
空间复杂度:O(1)。只使用了几个指针变量,没有使用额外的数组或哈希表。
cpp
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
// 定义快慢指针,初始都指向头节点
ListNode* fast = head;
ListNode* slow = head;
while (fast != nullptr && fast->next != nullptr) {
// 慢指针走一步
slow = slow->next;
// 快指针走两步
fast = fast->next->next;
// 如果快慢指针相遇,说明链表中存在环
if (slow == fast) {
// 此时,我们需要找到环的入口
// 根据数学推导:
// 从头节点出发一个指针 index1
// 从相遇节点出发一个指针 index2
// 它们每次都走一步,最终会在环的入口处相遇
ListNode* index1 = fast;
ListNode* index2 = head;
while (index1 != index2) {
index1 = index1->next;
index2 = index2->next;
}
// 返回环的入口节点
return index2;
}
}
// 如果循环结束(fast 遇到 null),说明没有环
return nullptr;
}
};