LeetCode hot 100—环形链表 II

发布于:2025-03-05 ⋅ 阅读:(14) ⋅ 点赞:(0)

题目

给定一个链表的头节点  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
解释:链表中没有环。

分析

哈希表法

遍历链表中的每个节点并记录下来。一旦遇到了此前遍历过的节点,就可以判定链表中存在环,直接返回该节点。

时间复杂度:O(n),n为链表中节点的数目

空间复杂度:O(n)

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        // 使用哈希集合来存储已经访问过的节点
        std::unordered_set<ListNode*> visited;
        // 遍历链表
        while (head != nullptr) {
            // 检查当前节点是否已经在哈希集合中
            if (visited.count(head)) {
                // 如果存在,说明找到了环的入口节点,返回该节点
                return head;
            }
            // 将当前节点插入到哈希集合中
            visited.insert(head);
            // 移动到下一个节点
            head = head->next;
        }
        // 如果遍历完整个链表都没有找到环,返回 nullptr
        return nullptr;
    }
};

快慢指针法

判断是否有环:首先使用快慢指针判断链表中是否存在环。slow 指针每次移动一步,fast 指针每次移动两步,如果它们相遇,则说明链表中存在环。

寻找环的入口:当快慢指针相遇后,将 slow 指针重新指向链表头节点,然后 slow 和 fast 指针以相同的速度(每次移动一步)继续移动,它们再次相遇的节点就是环的入口节点。

数学推导

设链表的头节点为 head,链表中环的入口节点为 entry,从链表头到环入口的节点数为 a,从环入口到快慢指针相遇点的节点数为 b,从快慢指针相遇点再到环入口的节点数为 c,则环的长度为 b + c

慢指针 slow 每次移动一步,快指针 fast 每次移动两步。当快慢指针相遇时,假设慢指针移动了 a + b 步,快指针移动了 y 步。因为快指针速度是慢指针的两倍,所以 y = 2(a+b)

又因为快指针比慢指针多走了若干圈环,设快指针在环里走了 n 圈(n 为正整数),则有 y = a + n(b + c) + b。将 y = 2(a+b) 代入 y = a + n(b + c) + b 可得:a = c + (n - 1)(b + c)

于是从相遇点到入环点的距离加上 n−1 圈的环长,恰好等于从链表头部到入环点的距离。即当 slow 和 fast 再次相遇时,相遇的节点就是环的入口节点

时间复杂度:O(n),n为链表中节点的数目

空间复杂度:O(1)

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        // 如果链表为空或者只有一个节点,肯定不存在环,返回 nullptr
        if (head == nullptr || head->next == nullptr) {
            return nullptr;
        }
        // 定义快慢指针
        ListNode* slow = head;
        ListNode* fast = head;
        // 标记是否有环
        bool hasCycle = false;
        // 移动快慢指针,快指针每次移动两步,慢指针每次移动一步
        while (fast != nullptr && fast->next != nullptr) {
            slow = slow->next;
            fast = fast->next->next;
            // 如果快慢指针相遇,说明存在环
            if (slow == fast) {
                hasCycle = true;
                break;
            }
        }
        // 如果没有环,返回 nullptr
        if (!hasCycle) {
            return nullptr;
        }
        // 让慢指针回到头节点
        slow = head;
        // 快慢指针以相同速度移动,再次相遇的节点就是环的入口节点
        while (slow != fast) {
            slow = slow->next;
            fast = fast->next;
        }
        return slow;
    }
};

网站公告

今日签到

点亮在社区的每一天
去签到