LeetCode 141. 环形链表:两种解题思路详解

LeetCode 入门级链表题------141. 环形链表,这道题是链表环判断的经典题型,考察对链表遍历和环结构的理解,同时有两种常用解题思路,适合新手巩固基础,也适合老手快速复盘,话不多说,直接开干!

一、题目解读

题目很简单:给你一个单链表的头节点 head,让你判断这个链表里面有没有"环"。

什么是链表中的环?举个例子:正常的链表是"一条路走到底",最后一个节点的 next 是 null,走到头就结束了;但如果有环,就相当于"走迷宫走到了死循环",某个节点的 next 会指向前面已经走过的节点,导致你沿着 next 指针一直走,永远走不到头。

这里要注意一个细节:题目里提到的 pos 参数,是系统用来标识环的位置的(比如 pos=2 表示链表尾连接到索引为2的节点),但我们写代码的时候不会用到这个参数,只需要通过链表本身的结构来判断是否有环即可。

最终要求:有环返回 true,无环返回 false。

二、前置知识

题目给出了单链表的节点定义,我们先看懂这个定义,后续代码才好理解:

typescript 复制代码
class ListNode {
  val: number
  next: ListNode | null
  constructor(val?: number, next?: ListNode | null) {
    this.val = (val === undefined ? 0 : val) // 节点的值,默认0
    this.next = (next === undefined ? null : next) // 指向的下一个节点,默认null(无下一个节点)
  }
}

简单说:每个 ListNode 节点有两个属性,val 存值,next 存下一个节点的引用(没有就为 null)。我们遍历链表,就是通过不断访问 node.next 来实现的。

三、解题思路1:哈希表法

3.1 思路核心

利用哈希表存储我们已经遍历过的节点。遍历链表时,每遇到一个节点,就检查它是否已经在哈希表中:

  • 如果在,说明我们之前已经走过这个节点了,现在又走回来了------存在环,直接返回 true;

  • 如果不在,就把这个节点加入 Set,继续遍历下一个节点;

  • 如果遍历到 node 为 null(走到链表末尾),说明没有环,返回 false。

这个思路的本质:用空间换时间,通过记录已走过的节点,避免重复遍历,从而判断是否有环。

3.2 完整代码

typescript 复制代码
function hasCycle_1(head: ListNode | null): boolean {
  const set = new Set(); // 用于存储已遍历的节点
  let node: ListNode | null = head; // 遍历指针,从表头开始
  while (node) { // 只要节点不为null,就继续遍历
    if (set.has(node)) { // 检查当前节点是否已存在(走过)
      return true; // 存在,说明有环
    }
    set.add(node); // 不存在,加入Set
    node = node.next; // 移动到下一个节点
  }
  return false; // 遍历结束,没遇到重复节点,无环
}

3.3 思路解析&易错点

✅ 易错点1:Set 中存储的是「节点本身」,而不是节点的 val!因为不同节点可能有相同的 val,但只要节点引用不同,就不是同一个节点(比如两个 val=1 的节点,next 不同,就是两个独立节点)。如果存 val,会出现误判。

✅ 易错点2:遍历指针的初始值是 head,循环条件是 node(不是 node.next),因为如果 head 本身就是 null(空链表),直接返回 false,避免空指针报错。

✅ 时间复杂度:O(n),n 是链表的节点数,每个节点只遍历一次,Set 的 add 和 has 操作都是 O(1);

✅ 空间复杂度:O(n),最坏情况下(无环),需要存储所有 n 个节点。

四、解题思路2:快慢指针法

4.1 思路核心

这个思路也叫「龟兔赛跑」,是链表环判断的最优解------不需要额外空间,仅用两个指针就能实现。

定义两个指针,慢指针(slow)和快指针(fast),初始都指向 head:

  • 慢指针每次走 1 步(slow = slow.next);

  • 快指针每次走 2 步(fast = fast.next.next);

  • 遍历链表,如果链表中存在环,那么快慢指针一定会在环内相遇(就像跑步时,快的人绕圈跑,总会追上慢的人);

  • 如果链表中没有环,快指针会先走到链表末尾(fast 或 fast.next 为 null),此时返回 false。

4.2 完整代码

typescript 复制代码
function hasCycle_2(head: ListNode | null): boolean {
  let slow = head; // 慢指针,初始指向表头
  let fast = head; // 快指针,初始指向表头
  while (fast && fast.next) { // 快指针能走两步,避免空指针报错
    slow = slow ? slow.next : null; // 慢指针走1步(处理slow为null的极端情况)
    fast = fast.next.next; // 快指针走2步
    if (slow === fast) { // 快慢指针相遇,说明有环
      return true;
    }
  }
  return false; // 快指针走到末尾,无环
}

4.3 思路解析&易错点

✅ 核心疑问:为什么有环的情况下,快慢指针一定会相遇?

简单解释:假设环的长度为 L,当慢指针刚进入环时,快指针已经在环内某个位置了。之后,快指针每次比慢指针多走 1 步(2步 - 1步),相当于快指针在以"每次1步"的速度追赶慢指针。因为环是闭合的,没有尽头,所以快指针一定会追上慢指针,也就是两者相遇。

✅ 易错点1:循环条件是 fast && fast.next,而不是 slow && fast!因为快指针走得快,如果 fast 是 null,或者 fast.next 是 null,说明快指针已经走到末尾,再走一步就会报错,此时直接判断无环。

✅ 易错点2:slow = slow ? slow.next : null 的处理。虽然初始时 slow 和 fast 都是 head,且循环条件保证了 fast 和 fast.next 不为 null,但极端情况下(比如 head 是 null),slow 可能为 null,加上这个判断可以避免空指针报错(简化写法也可以直接写 slow = slow.next,因为循环条件会保证 slow 不会提前为 null)。

✅ 时间复杂度:O(n),n 是链表节点数。最坏情况下,慢指针走完整个链表,快指针在环内多绕几圈,但总体还是线性时间;

✅ 空间复杂度:O(1),只用到了两个指针,没有额外开辟空间,比哈希表法更优。

五、两种思路对比(选择建议)

解题方法 时间复杂度 空间复杂度 适用场景
哈希表法(hasCycle_1) O(n) O(n) 新手入门、不需要优化空间、想快速写出正确代码
快慢指针法(hasCycle_2) O(n) O(1) 面试最优解、空间受限场景、进阶优化

六、总结&拓展

这道题的核心是「判断链表是否存在环」,两种思路各有优劣,但快慢指针法是面试中的重点,一定要掌握其原理(龟兔赛跑的逻辑)。

相关推荐
mCell4 小时前
如何零成本搭建个人站点
前端·程序员·github
mCell5 小时前
为什么 Memo Code 先做 CLI:以及终端输入框到底有多难搞
前端·设计模式·agent
恋猫de小郭5 小时前
AI 在提高你工作效率的同时,也一直在增加你的疲惫和焦虑
前端·人工智能·ai编程
寻寻觅觅☆5 小时前
东华OJ-基础题-106-大整数相加(C++)
开发语言·c++·算法
少云清5 小时前
【安全测试】2_客户端脚本安全测试 _XSS和CSRF
前端·xss·csrf
银烛木5 小时前
黑马程序员前端h5+css3
前端·css·css3
m0_607076605 小时前
CSS3 转换,快手前端面试经验,隔壁都馋哭了
前端·面试·css3
听海边涛声5 小时前
CSS3 图片模糊处理
前端·css·css3
IT、木易5 小时前
css3 backdrop-filter 在移动端 Safari 上导致渲染性能急剧下降的优化方案有哪些?
前端·css3·safari
偷吃的耗子5 小时前
【CNN算法理解】:三、AlexNet 训练模块(附代码)
深度学习·算法·cnn