LeetCode 142. 环形链表 II(Floyd 判圈算法、哈希表)

0 阅读3分钟

image.png

image.png

一、题目大意

给定链表头结点head,找出链表环的入口节点;链表无环返回null

注意:判断的是节点引用 (内存地址),不是节点数值;不允许修改链表。

二、解题思路

方法 1:Floyd 快慢指针(O (1) 空间,面试最优)

分为两个阶段:

  1. 阶段 1:找相遇点 slow 每次走 1 步,fast 每次走 2 步。 如果存在环,快慢指针一定会在环内某个节点相遇;如果 fast 走到null,代表无环。

⚠️重点:相遇点≠环入口,不能直接返回相遇节点。

  1. 阶段 2:寻找环入口 数学推导结论:头结点到环入口距离 = 相遇点继续走到环入口的距离(允许绕完整的环)。
  • 将 slow 指针重置到链表头部,fast 停留在相遇点
  • slow、fast 两个指针每次都只走 1 步
  • 再次相遇的节点,就是环入口。

数学简要推导

  • a:头到环入口距离;b:环入口到相遇点;c:环长度
  • (slow: a+b);(fast:2(a+b))
  • (2(a+b)=a+b+n\cdot c),化简得到 (\boldsymbol{a = n\cdot c-b})

完整 Java 代码

public class Solution {
    public ListNode detectCycle(ListNode head) {
        ListNode slow = head;
        ListNode fast = head;
        //阶段1:快慢指针找相遇点
        while(fast != null && fast.next != null){
            slow = slow.next;
            fast = fast.next.next;
            //发生相遇,存在环
            if(slow == fast){
                slow = head; //slow放回表头
                //两个指针每次走一步
                while(slow != fast){
                    slow = slow.next;
                    fast = fast.next;
                }
                return slow; //再次相遇即为环入口
            }
        }
        //fast走到null,链表无环
        return null;
    }
}

三、实例演示

样例链表:3 → 2 → 0 → -4-4.next =2,环入口节点为 2

阶段 1 找相遇点

轮次slowfast
初始33
第 1 轮20
第 2 轮02
第 3 轮-4-4

✅相遇点:-4(不是环入口)

阶段 2 同步一步一步走

轮次slow (重置 head=3)fast (停留在 - 4)判断
初始3-4不相等
走一步22✅相等,返回节点 2

四、方法 2:HashSet 哈希表(空间 O (n),逻辑简单)

核心思想

遍历链表,把节点对象(引用)存入集合。 第一次遇到已经存在集合中的节点,就是环入口;遍历到 null 代表无环。

⚠️注意:存的是节点,不是节点 val;val 重复不等于链表有环。

public class Solution {
    public ListNode detectCycle(ListNode head) {
        Set<ListNode> visited = new HashSet<>();
        ListNode cur = head;
        while(cur != null){
            if(visited.contains(cur)){
                return cur;
            }
            visited.add(cur);
            cur = cur.next;
        }
        return null;
    }
}

实例演示: cur 依次遍历3、2、0、-4加入集合;当 cur 再次走到2,集合中已经存在节点 2,直接返回节点 2。

五、两种方法对比

方案时间复杂度空间复杂度特点
Floyd 快慢指针O(n)O(1)原地算法,面试首选;需要理解数学推导
HashSet 哈希表O(n)O(n)逻辑简单,不容易写错;消耗额外内存

六、语法坑与边界总结

  1. while 循环条件顺序:fast != null && fast.next != null,顺序不能颠倒,否则会空指针异常。

  2. 第一次快慢指针相遇 ≠ 环入口,千万不能直接 return。

  3. 第二阶段两个指针全部只能走一步,fast 不要再执行next.next

  4. 特殊边界:

    • 整个链表自环(head 就是环入口),相遇后直接返回 head;
    • 无环链表,fast 走到 null 返回 null;
  5. HashSet 比较的是节点内存地址,不是 val,避免数值相同造成误判。

七、个人感悟

141 题只需要判断是否存在环;142 题需要找到环入口,多了第二阶段同步遍历。快慢指针的结论需要记住:相遇后,一个指针放回表头,两者同步前进,再次相遇点就是环入口。哈希表写题很快,但面试优先考察 O (1) 空间的 Floyd 算法。