LeetCode 141:环形链表

LeetCode 141:环形链表------用快慢指针判断链表是否成环

一、题目描述

今天做的是 LeetCode 141:环形链表

题目要求判断一个单链表中是否存在环。

所谓环,就是链表中的某个节点的 next 指针重新指向了前面的某个节点。

例如正常链表:

text 复制代码
1 → 2 → 3 → 4 → null

最终会走到 null,所以不存在环。

而如果:

text 复制代码
1 → 2 → 3 → 4
        ↑   ↓
        ← ←

链表就会一直循环下去。

要求返回:

text 复制代码
存在环 → true
不存在环 → false

二、我的思路:快慢指针

看到这道题,我想到的是链表中比较经典的:

快慢指针。

定义两个指针:

java 复制代码
ListNode fast = head;
ListNode slow = head;

其中:

text 复制代码
slow:每次走一步

fast:每次走两步

所以遍历的时候:

java 复制代码
fast = fast.next.next;
slow = slow.next;

如果链表没有环:

text 复制代码
slow
 ↓
1 → 2 → 3 → 4 → null

fast
 ↓

最终 fast 会走到 null

而如果链表存在环,那么进入环以后:

text 复制代码
slow → →
fast → → →

虽然 fast 一开始领先 slow,但是因为两个人都在一个环里面不断移动,而且 fast 每次比 slow 多走一步,所以最终一定会追上 slow

因此判断逻辑就很简单:

text 复制代码
fast 到 null
    ↓
没有环

fast == slow
    ↓
存在环

三、我的代码

最终代码:

java 复制代码
public class Solution {
    public boolean hasCycle(ListNode head) {

        // 链表为空,或者只有一个节点
        // 都不可能形成环
        if(head == null || head.next == null){
            return false;
        }

        ListNode fast = head;
        ListNode slow = head;

        while(fast != null && slow != null){

            // fast 如果已经没有下一个节点,
            // 说明链表到头了,不存在环
            if(fast.next == null){
                return false;
            }

            // fast 一次走两步
            fast = fast.next.next;

            // slow 一次走一步
            slow = slow.next;

            // 两个指针相遇,说明存在环
            if(fast == slow){
                return true;
            }
        }

        return false;
    }
}

四、为什么 fast 要检查 fast.next

这里我觉得是代码中最需要注意的地方:

java 复制代码
if(fast.next == null){
    return false;
}

因为下面马上要执行:

java 复制代码
fast = fast.next.next;

如果:

text 复制代码
fast.next == null

那么继续访问:

java 复制代码
fast.next.next

就会出现空指针问题。

所以在让 fast 一次走两步之前,至少需要保证:

text 复制代码
fast.next 存在

也就是:

java 复制代码
if(fast.next == null){
    return false;
}

五、为什么 slow 不需要单独判断 slow.next

代码中:

java 复制代码
while(fast != null && slow != null)

其实这里 slow != null 的判断有点多余。

原因是:

fast 的速度比 slow 快。

如果 slow 已经走到了 null,那么 fast 肯定也不可能还在正常链表中继续运行。

而真正可能出现的问题是:

java 复制代码
fast.next == null

因为 fast 一次要走两步。

所以代码实际上可以稍微简化成:

java 复制代码
while(fast != null && fast.next != null){
    fast = fast.next.next;
    slow = slow.next;

    if(fast == slow){
        return true;
    }
}

return false;

这是我更推荐记住的写法。


六、为什么两个指针相遇就一定有环?

这个问题是这道题最核心的地方。

假设链表没有环:

text 复制代码
1 → 2 → 3 → 4 → null

无论 fast 走得多快,最终都会走到 null

所以:

text 复制代码
没有环 → fast 到 null

但是如果存在环:

text 复制代码
1 → 2 → 3 → 4
        ↑     ↓
        ← ← ←

slowfast 都进入环以后,可以把它们理解成在一个跑道上跑步:

text 复制代码
slow:每次走 1 格
fast:每次走 2 格

那么每一轮:

text 复制代码
fast 比 slow 多走 1 格

假设环长度为 5,两个人之间相差 3 格。

那么:

text 复制代码
第 1 次:相差 2
第 2 次:相差 1
第 3 次:相差 0

最终就会相遇。

所以:

只要两个指针都进入同一个环,并且快指针比慢指针每次多走一步,那么快指针最终一定能够追上慢指针。

这就是 Floyd 判圈算法的核心。


七、这道题其实不需要额外的数据结构

如果使用 HashSet,也可以判断链表是否有环。

思路是:

text 复制代码
遍历节点
↓
把访问过的节点放进 Set
↓
如果某个节点已经存在
↓
说明出现重复
↓
存在环

但是这样需要保存访问过的节点:

text 复制代码
O(n)

而快慢指针只需要:

java 复制代码
ListNode fast;
ListNode slow;

所以额外空间复杂度是:

text 复制代码
O(1)

时间复杂度:

text 复制代码
O(n)

因此快慢指针更加适合这道题。


八、这次对快慢指针的理解

之前遇到链表题的时候,我更多把快慢指针理解成一种"固定套路":

text 复制代码
一个走一步
一个走两步

但这道题让我更加理解了它为什么能够解决问题。

关键其实不是:

"快指针和慢指针速度不同。"

而是:

当它们进入环以后,速度差会不断缩小它们之间的距离。

在环中:

text 复制代码
slow 每次 +1
fast 每次 +2

所以相对运动速度就是:

text 复制代码
2 - 1 = 1

只要环存在,最终一定会相遇。


九、总结

这道题的代码并不长,但它是一个非常经典的链表问题。

我的最终思路可以概括成:

text 复制代码
slow 每次走一步
fast 每次走两步
        ↓
如果 fast 到达 null
        ↓
没有环

如果 fast == slow
        ↓
存在环

最终复杂度:

text 复制代码
时间复杂度:O(n)
空间复杂度:O(1)

这道题最大的收获就是理解了快慢指针为什么能够判断环,而不只是记住:

java 复制代码
fast = fast.next.next;
slow = slow.next;

另外,自己的代码虽然可以通过,但:

java 复制代码
while(fast != null && slow != null)

中的 slow != null 实际上可以省略。

更常见、也更简洁的写法是:

java 复制代码
while(fast != null && fast.next != null){
    fast = fast.next.next;
    slow = slow.next;

    if(fast == slow){
        return true;
    }
}
return false;

这次我也算是把一个新的链表技巧真正记下来了:

快慢指针不仅可以用来找链表中点,也可以用来判断链表是否存在环。

相关推荐
小小猪的春天42 分钟前
Java 手写第一个 MCP Server:Spring AI MCP 半小时跑通
java·人工智能·spring boot·ai编程
迅翼SwiftWing1 小时前
编队系列(总)|固定翼编队如何实现稳定飞行?通信架构、协同算法与工程约束深度解析
算法·架构
LB21121 小时前
力扣160 21 86
算法·leetcode·职场和发展
今天AI了吗1 小时前
DeepSeek Harness 深度解析:从评测架构到实战落地
java·网络·数据库·人工智能·架构·java-ee
许彰午2 小时前
27-AuthService登录链路
java·低代码·架构
泡海椒2 小时前
JQuick-Curl 性能分析:并发场景下的性能表现与调优,第三方接口调用不只要快写也要稳跑
java·开发语言·okhttp
小智老师PMP2 小时前
2026深度解析|PMP第八版与NPDP核心侧重点本质区别(管理类证书怎么选)
开发语言·分布式·算法·职场和发展·产品经理
heima20162 小时前
长期复盘:餐饮店活动拓客链接的行业现状与使用困境
链表
HugoStudio_SWAN2 小时前
洛谷 B4450 / B3867 / B3923 智慧购物、储蓄与做题——从程序到生活
c++·学习·程序人生·算法·生活