环形链表刷题笔记(LeetCode热题100--141、142)

环形链表刷题笔记(LeetCode热题100--141、142)

一、核心概念

1.1 环形链表的定义

  • 环形链表:链表中存在一个节点,可以通过连续追踪 next 指针再次到达
  • 环入口:进入环的第一个节点
  • 环长度:环中节点的个数

1.2 快慢指针原理

  • 快指针:每次走两步
  • 慢指针:每次走一步
  • 核心思想:如果有环,快指针最终会追上慢指针(相遇)

二、两道经典题目对比

2.1 141. 环形链表(简单)

目标:只判断链表是否有环

方法一:哈希表法
cpp 复制代码
bool hasCycle(ListNode *head) {
    unordered_set<ListNode*> set;
    while(head != NULL) {
        if(set.count(head)) return true;  // 节点已存在,说明有环
        set.insert(head);
        head = head->next;
    }
    return false;  // 遍历到链表尾部,无环
}
  • 时间复杂度:O(n)
  • 空间复杂度:O(n)
  • 优点:思路简单直接
  • 缺点:需要额外空间
方法二:快慢指针法(推荐)
cpp 复制代码
bool hasCycle(ListNode *head) {
    if(head == nullptr || head->next == nullptr) return false;
    
    ListNode *f = head->next;  // 快指针(先走一步)
    ListNode *s = head;        // 慢指针
    
    while(s != f) {
        if(f == nullptr || f->next == nullptr) return false;
        s = s->next;
        f = f->next->next;
    }
    return true;  // 快慢指针相遇,说明有环
}
  • 时间复杂度:O(n)
  • 空间复杂度:O(1)
  • 注意:快指针从 head->next 开始,避免第一次就相等

2.2 142. 环形链表 II(中等)

目标:找出环形链表的环入口节点

Floyd算法实现
cpp 复制代码
ListNode *detectCycle(ListNode *head) {
    if(head == nullptr) return nullptr;
    
    ListNode *s = head;  // 慢指针
    ListNode *f = head;  // 快指针
    
    // 第一阶段:检测是否有环
    while(true) {
        if(f == nullptr || f->next == nullptr) return nullptr;  // 无环
        s = s->next;
        f = f->next->next;
        if(f == s) break;  // 有环,相遇
    }
    
    // 第二阶段:找到环入口
    f = head;  // 快指针重新指向头部
    while(s != f) {
        s = s->next;
        f = f->next;
    }
    return f;  // 相遇点就是环入口
}

三、算法推导与证明

3.1 数学推导

slow指针每次走1步、fast指针每次走2步

如果fast 指针走过链表末端,说明链表无环,此时直接返回 null。

否则存在环,并且f快,s慢,当它们进入环后,相当于追及问题,二者一定会相遇:

我们设:

  • 从 head 到环入口的距离为 a(你图中 a=3)
  • 环的长度为 b(你图中 b=4)
  • 第一次相遇时,slow 指针走了 s 步,因为 slow 和 fast 指针走的时间相同,但 fast 指针的速度是 slow 的2倍。所以,fast 指针走了 f = 2s​ 步。
  • fast 比 slow 多走了 n 个环的长度,即 f=s+nb;( 解析: 双指针都走过 a 步,然后在环内绕圈直到重合,重合时 fast 比 slow 多走 环的长度整数倍 )。
    将以上两式相减得到 f=2nb,s=nb,即 fast 和 slow 指针分别走了 2n,n 个环的周长。
  • 如果让指针从链表头部一直向前走并统计步数k,那么所有 走到链表入口节点时的步数 是:k=a+nb ,即先走 a 步到入口节点,之后每绕 1 圈环( b 步)都会再次到入口节点。
  • 而目前 slow 指针走了 nb 步。因此, slow 再走 a 步即可到达环的入口。
  • a是未知数,我们让fast指针指向head结点,再依次遍历链表,直到走了a步到达环的入口,此时slow也走了a步,二者相遇,得到结果。

3.2 图示理解

text 复制代码
slow(s)​ 指针每次走 1 步
fast(f)​ 指针每次走 2 步(f = 2s)
head​ 是链表起点
a = 3:从 head 到环入口的距离(节点1→2→3→4,共3步)
b = 4:环的长度(节点4→5→6→7→8→4,共4个节点)
第一次相遇​ 在节点6(环内某点)
第二次相遇​ 在节点4(环入口)------此时将 slow 从 head 重新出发,fast 从第一次相遇点出发,
都每次走1步,相遇点即为环入口

四、解题要点总结

4.1 边界条件处理

  1. 空链表 :直接返回 false 或 nullptr
  2. 单节点 :检查 head->next 是否为空
  3. 快指针判空 :必须检查 fast 和 fast->next 是否为空

4.2 代码实现技巧

  1. 141题的简洁写法
cpp 复制代码
bool hasCycle(ListNode *head) {
    ListNode *slow = head, *fast = head;
    while(fast && fast->next) {
        slow = slow->next;
        fast = fast->next->next;
        if(slow == fast) return true;
    }
    return false;
}
  1. 142题的关键点
cpp 复制代码
f = head;  // 重置快指针到头部
while(s != f) {  // 同速前进
    s = s->next;
    f = f->next;
}

4.3 常见错误

  1. 忘记检查 fast->next 是否为空
  2. 141题中快慢指针起始位置相同导致误判
  3. 142题中忘记在找到环后重置指针

五、面试常见问题

5.1 时间复杂度分析

  • 141题:O(n),最坏情况下遍历整个链表
  • 142题:O(n),两阶段都不会超过链表长度

5.2 空间复杂度分析

  • 哈希表法:O(n)
  • 快慢指针法:O(1) ← 推荐

5.3 扩展问题

  1. 如何求环的长度?

    • 相遇后,固定一个指针,另一个继续走直到再次相遇
  2. 如果快指针走3步、慢指针走1步,还能检测环吗?

    • 不一定,可能会错过

六、刷题建议

  1. 先理解原理:掌握Floyd算法的数学推导
  2. 熟练两种方法:哈希表法和快慢指针法
  3. 注意边界条件:空链表、单节点链表
  4. 画图分析:在纸上画出指针移动过程
  5. 代码模板:熟记142题的代码结构,可以应对大部分变体

总结:环形链表问题考察对指针操作和数学推导的理解。141题是基础,142题是进阶,掌握快慢指针法是解决这类问题的核心技能。

相关推荐
额额额对了1 小时前
Linux 驱动开发:基于 platform 总线 + misc 设备的 DS18B20 单总线温度驱动
linux·c语言·开发语言·驱动开发·嵌入式硬件
奶盐cookie1 小时前
内存区域:栈、全局区和静态区
c语言
xxwl5851 小时前
第六章---计算几何
笔记·学习·算法
头像很酷1 小时前
嵌入式虚拟化-xvisor-跨越边界的读客
c语言·hypervisor·xvisor·嵌入式虚拟化
Logic1012 小时前
C语言/数据结构二维数组题解:找出行列O个数均为偶数的格子——奇偶性预处理+二次遍历
c语言·数据结构·算法·二维数组·时间复杂度·动态内存分配·奇偶性
程序猿阿森2 小时前
【机器学习实战】朴素贝叶斯算法详解:从概率基础到商品评论情感分析
算法·机器学习·概率论
余额瞒着我当琳2 小时前
哈希表底层原理解析,直接定址法,哈希冲突,负载因子,哈希函数,开放定址法
java·c++·哈希算法·散列表
xxwl5852 小时前
区域赛算法模版第五章---数学与数论
c++·笔记·算法
看浪的路人3 小时前
第6讲:Agent 安全体系——Prompt 注入防御、工具调用审计与输出合规
人工智能·算法
局部有解3 小时前
LeetCode Hot100 438 找到字符串中所有字母异位词
c++·算法·leetcode