LeetCode Hot 100 —— 141.环形链表

141.环形链表

给你一个链表的头节点 head ,判断链表中是否有环。

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。

如果链表中存在环 ,则返回 true 。 否则,返回 false
示例 1:
img

输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
img

输入:head = [1,2], pos = 0
输出:true
解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:
img

输入:head = [1], pos = -1
输出:false
解释:链表中没有环。

方法一:遍历法(不推荐,反面教材)

遇事不决就开始遍历,肉眼可见的是一个笨方法,不过也可以通过。

bool hasCycle(struct ListNode *head) {
    if(head == NULL || head->next == NULL) return 0;
    struct ListNode *temp = head; 
    struct ListNode *pos_head = (struct ListNode*)malloc(sizeof(struct ListNode));
    pos_head->next = NULL;          // 初始为空,不预先塞入 head
    struct ListNode *pos;           // 只声明,不赋值

    while(temp != NULL)
    {
        // 先检查当前 temp 是否已经在记录表中
        pos = pos_head->next;
        while(pos != NULL)
        {
            if((int)temp == pos->val) return 1;
            pos = pos->next;
        }

        // 检查通过后,将当前 temp 的地址存入记录表
        pos = (struct ListNode*)malloc(sizeof(struct ListNode)); // 直接复用 pos
        pos->val = (int)temp;
        pos->next = pos_head->next;
        pos_head->next = pos;

        // 移动到下一个原始节点
        temp = temp->next;
    }
    return 0;
}

思路分析:
遍历链表,每访问一个节点,就将该节点的地址记录下来。之后每次访问新节点前,先检查该地址是否已存在于记录中,若存在则说明有环。

注意事项:

  1. (int)temp 将指针强制转换为 int 类型,在 64 位系统上会导致指针地址截断(int 为 32 位,指针为 64 位),可能产生误判。建议使用 uintptr_t 来完整保存地址。

  2. 额外开辟的记录节点未被释放,存在内存泄漏

  3. 时间复杂度 O(n²),空间复杂度 O(n),效率较低。

该方法仅作概念演示,实际工程中不推荐使用。

方法二:快慢指针(Floyd 判圈算法)

bool hasCycle(struct ListNode* head) {
    if (head == NULL || head->next == NULL) {
        return false;
    }
    struct ListNode* slow = head;
    struct ListNode* fast = head->next;
    while (slow != fast) {
        if (fast == NULL || fast->next == NULL) {
            return false;
        }
        slow = slow->next;
        fast = fast->next->next;
    }
    return true;
}

思路分析:
Floyd 判圈算法(快慢指针)的核心思想是:使用两个指针 slowfast 同时遍历链表,slow 每次走一步,fast 每次走两步。如果链表中存在环,则 fast 最终一定会追上 slow(二者相遇);如果无环,则 fast 会先到达链表末尾(NULL)。

因为对于快指针来说,最终相遇的时候一定是走过了slow两倍的距离,也就是如果有环一定会相遇。

过程简述:

  • 若无环,fast 先到达末尾,循环退出,返回 false
  • 若有环,fast 进入环后会在环中绕圈,而 slow 最终也会进入环。此时 fast 相对于 slow 的速度为 1 步/次fast 每次比 slow 多走一步),因此 fast 必定能在有限步内追上 slow

复杂度分析:

  • 时间复杂度: O(n),其中 n 为链表节点数。无环时只需遍历到末尾;有环时,slowfast 相遇前遍历的步数不超过 n。
  • 空间复杂度: O(1),只使用了两个指针变量,无需额外存储空间。
posted @ 2026-07-19 14:36  Javenwww  阅读(3)  评论(0)    收藏  举报