链表偷偷“绕圈圈”?Java用2个指针抓现行,不小心就卡死程序!

你有没有遇到过这种奇葩情况:写了个遍历链表的代码,结果程序卡到没反应,控制台疯狂转圈,最后报个“栈溢出”?大概率是链表偷偷形成了“环”——就像迷宫里的死循环,指针进去就绕不出来了!今天用“操场跑步抓小偷”的段子给你讲透怎么揪出环,两个指针搞定,看完笑到会写代码,再也不怕程序被“环”卡死~

先懂“链表环”:像小朋友手拉手围成圈,进去就出不来

正常的链表是“一条道走到黑”,最后一个节点的`next`指向`null`,就像小朋友排队一直往前走,最后一个人后面没人。但如果某个节点的`next`指向了前面的某个节点,就形成了“环”——相当于排尾的小朋友突然抓住了排头的手,围成一个圈,你从任何一个人开始数,永远数不完,指针遍历到这也会卡死!

举个栗子:链表`1→2→3→4→2`,4的`next`不是`null`而是2,就形成了`2→3→4→2`的环。指针走到2,就会在2→3→4→2之间无限循环,程序要么卡死,要么抛出异常,新手遇到这种情况,往往会懵圈:“我代码没写错啊,怎么跑不完?”

抓环神器:快慢指针像“警察抓小偷”,同圈必相遇

判断链表有没有环,最牛的方法是“快慢指针”,原理超简单:让两个指针在链表上跑,一个慢(一次走1步),一个快(一次走2步)。如果链表有环,快指针迟早会追上慢指针(就像跑步时快的总能套慢的一圈);如果没环,快指针会先跑到终点(`null`)。

这就像操场抓小偷:

- 你(快指针)一步跨2阶,小偷(慢指针)一步跨1阶,同时从起点出发;

- 如果操场是环形(有环),你速度比小偷快,迟早会追上他(相遇);

- 如果操场是直线(没环),你会先跑到终点,小偷还在后面磨蹭。

Java实现3步走:操场跑步 analogy 全还原

用“快慢指针”判断环,代码简单到离谱,三步就能搞定,对应操场抓小偷的场景:

1. 初始化“快慢选手”:找两个指针当“跑步者”

先定义两个指针,都从链表头节点出发:

- `slow`(慢指针):一次走1步,像“小乌龟”;

- `fast`(快指针):一次走2步,像“小兔子”。

Java代码:

// 定义链表节点
class ListNode {
        int val;
        ListNode next;
        ListNode(int val) { this.val = val; }
        }
  public class HasCycle {
          public boolean hasCycle(ListNode head) {
                  if (head == null || head.next == null) {
                  return false; // 空链表或只有一个节点,肯定没环
                  }
                ListNode slow = head; // 慢指针(小乌龟)
                ListNode fast = head.next; // 快指针(小兔子),先多走一步,避免初始就相遇
                // 后面步骤写这里...
          }
}

2. 让“选手”开始跑步:循环移动指针

让两个指针同时在链表上跑,`slow`每次走1步(`slow = slow.next`),`fast`每次走2步(`fast = fast.next.next`)。就像小乌龟和小兔子同时出发,一个慢一个快。

循环条件:只要`fast`和`slow`没相遇,就一直跑。

代码:

while (slow != fast) { // 没相遇就继续跑
        // 快指针跑到头了(没环),直接返回false
        if (fast == null || fast.next == null) {
    			    return false;
        }
        slow = slow.next; // 小乌龟走1步
        fast = fast.next.next; // 小兔子走2步
}

3. 判断是否相遇:相遇就是有环,跑到头就是没环

如果`fast`和`slow`相遇了(`slow == fast`),说明链表有环(就像小兔子追上了小乌龟,证明操场是环形);如果`fast`跑到`null`了(`fast == null`或`fast.next == null`),说明没环(直线跑道,小兔子先到终点)。

最后返回`true`(相遇=有环):

return true; // 跳出循环说明slow == fast,有环!

完整代码加起来不到20行,却能高效判断环,这就是“算法的魔力”!

避坑提醒:这2个坑能让“抓环”失败,新手必踩!

1. **初始指针位置错:两个指针都从head出发**

如果你让`slow`和`fast`一开始都指向`head`(`fast = head`),而链表刚好头节点自己形成环(`head.next = head`),第一次循环就会判定`slow == fast`,正确返回有环。但如果是正常环,比如`1→2→3→1`,这种初始位置也能判断。不过更规范的是让`fast`先多走一步(`fast = head.next`),避免一些边界判断失误,就像跑步让快的人稍微领先一点,规则更清晰。

2. **没判断fast.next是否为null:快指针“踩空”**

`fast`每次走2步,必须确保`fast.next`不为`null`,不然`fast.next.next`会报空指针异常(`NullPointerException`)。就像小兔子跨2步时,得确保脚下有台阶,不然会摔沟里。所以循环里一定要先判断`fast == null || fast.next == null`,再移动指针。

为啥快慢指针这么牛?效率吊打“哈希表法”

判断环还有种方法是“哈希表记录走过的节点”:每遍历一个节点就存进HashSet,遇到重复节点就是有环。但这种方法要额外占O(n)的空间(存所有节点),就像跑步时带个笔记本记每一步,累得慌。

而快慢指针法只需要2个指针,空间复杂度O(1),时间复杂度O(n)(最多跑两圈),就像光凭腿跑步,不用带任何东西,轻量又高效——这就是算法的“四两拨千斤”!

互动时间:来测测你的“抓环实战力”!

1. 链表`1→2→3→4→2`(4指向2),用快慢指针判断,会相遇吗?相遇在哪个节点?(提示:画图模拟一下,慢指针走1步,快指针走2步)

2. 如果链表是`1→2→3→null`(无环),快指针会先跑到哪个位置?

3. 你写代码时遇到过“链表环导致卡死”的情况吗?当时排查了多久才发现是环的问题?评论区说说你的“抓环血泪史”!

评论区交出你的答案,前3名答对的送“Java链表调试手册”(含环检测、环入口查找的通俗讲解+代码模板)!关注我,下期揭秘“如何找到链表环的入口位置”——比判断环多一步,却能帮你彻底根治链表死循环,实用到爆~

原文链接:,转发请注明来源!