环形链表刷题笔记(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=2nbs=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. 空链表 :直接返回 falsenullptr
  2. 单节点 :检查 head->next 是否为空
  3. 快指针判空 :必须检查 fastfast->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题是进阶,掌握快慢指针法是解决这类问题的核心技能。

相关推荐
Kang.Charles2 小时前
UE4 C++将Json数据写入目标字符串
c++·json·ue4
LONGZETECH2 小时前
智能网联汽车故障诊断仿真教学软件技术解析——C/S架构落地与全模块实现
c语言·开发语言·架构·汽车·汽车仿真教学软件·汽车教学软件·智能网联汽车软件
滴滴答滴答答2 小时前
机考刷题之 13 LeetCode 1004 最大连续1的个数 III
java·算法·leetcode
一叶落4382 小时前
139. 单词拆分(Word Break)
c语言·数据结构·算法·leetcode·深度优先·图论
Yupureki2 小时前
《算法竞赛从入门到国奖》算法基础:数据结构-单调队列
c语言·数据结构·c++·算法
逆境不可逃2 小时前
【从零入门23种设计模式17】行为型之中介者模式
java·leetcode·microsoft·设计模式·职场和发展·中介者模式
LONGZETECH2 小时前
汽车车身测量与结构件更换仿真教学系统架构全解析
c语言·开发语言·架构·系统架构·汽车·汽车教学软件·智能网联汽车软件
重生之我是Java开发战士2 小时前
【递归、搜索与回溯】穷举,暴搜,深搜,回溯,剪枝:全排列与子集
算法·机器学习·剪枝
Yeats_Liao2 小时前
模型剪枝技术:结构化剪枝原理与推理加速实践
算法·机器学习·剪枝