804 字
4 分钟
环形链表 II

题目描述#

给定一个链表的头节点  head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null

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

不允许修改 链表。

解题思路#

  1. 双指针判环
    • 快指针(每次2步)和慢指针(每次1步)同时出发
    • 若两指针相遇则存在环
  2. 重置指针定位入口
    • 相遇后将慢指针重置到头节点
    • 两指针同速移动(每次1步)
  3. 二次相遇即入口
    • 当两指针再次相遇时即为环入口
  4. 无环处理
    • 若快指针先到链表尾则返回null

关键洞察#

  1. 数学关系核心
    • 设头到入口距离a,入口到相遇点距离b,相遇点到入口距离c
    • 快指针路程 = 2 × 慢指针路程 ⇒ a + k(b+c) + b = 2(a + b) ⇒ a = c + (k-1)环长
  2. 重置指针原理
    • 第二次同速移动时,两指针将在环入口相遇
  3. 通用性保证
    • 无论环大小如何均适用
    • 快慢指针首次相遇点不影响结论
  4. 边界处理
    • 单节点自环的特殊处理
    • 无环链表的快速判定

复杂度分析#

指标说明
时间复杂度O(n):每个节点最多遍历两次
空间复杂度O(1):仅需两个指针
最优情况O(n):当环在链表头部时

代码实现#

/**
* Definition for singly-linked list.
* function ListNode(val) {
* this.val = val;
* this.next = null;
* }
*/
/**
* @param {ListNode} head
* @return {ListNode}
*/
var detectCycle = function (head) {
let slow = head, fast = head;
while (true) {
if (!fast || !fast.next) return null;
slow = slow.next;
fast = fast.next.next;
if (slow === fast) break;
}
fast = head;
while (slow !== fast) {
slow = slow.next;
fast = fast.next;
}
return fast;
};

实际应用场景#

  1. 内存泄漏检测:检测程序中循环引用的对象链`
  2. 路由检测:网络路由中检测环形路径`
  3. 状态机验证:验证有限状态机是否陷于死循环`
  4. 链表操作安全:链表排序/合并前确保无环`

变种:求环长度#

const cycleLength = (head) => {
// 先找到相遇点
let slow = head, fast = head;
while (fast && fast.next) {
slow = slow.next;
fast = fast.next.next;
if (slow === fast) break;
}
// 无环返回0
if (!fast || !fast.next) return 0;
// 固定相遇点,移动指针计算环长
let current = slow;
let length = 0;
do {
current = current.next;
length++;
} while (current !== slow);
return length;
};

多指针实现对比#

方法空间复杂度是否修改链表适用场景
双指针法O(1)通用最优解
哈希表法O(n)需记录访问节点
标记节点法O(1)允许修改链表结构
破坏链表法O(1)只允许临时修改

相似题目#

环形链表 II
https://website-truelovings-projects.vercel.app/posts/algorithms/环形链表-ii/
作者
欢迎来到StarSky的网站!
发布于
2025-08-24
许可协议
CC BY-NC-SA 4.0