链表算法上篇:LeetCode 206/234/141/142/160/21 题解与易错点

文章目录

    • 前言
    • 一、基础认知:链表节点是引用,不是值
    • [二、206. 反转链表](#二、206. 反转链表)
    • [三、234. 回文链表](#三、234. 回文链表)
    • [四、141/142. 环形链表(检测 + 找入口)](#四、141/142. 环形链表(检测 + 找入口))
      • [141. 判断是否有环](#141. 判断是否有环)
      • [142. 找环的入口](#142. 找环的入口)
    • [五、160. 相交链表](#五、160. 相交链表)
    • [六、21. 合并两个有序链表](#六、21. 合并两个有序链表)
    • 七、错误总结

前言

这篇文章整理链表算法的六道经典题,帮你建立从"能写出来"到"不犯低级错误"的认知闭环。

刷链表题最大的坑不是思路,是细节:引用和值傻傻分不清、循环中途空指针、计数差一个......这些错误我都踩过,全记在这里。


一、基础认知:链表节点是引用,不是值

在讲题之前,必须先搞清一件事。

java 复制代码
ListNode a = node;
ListNode b = node;
a.next = null; // b.next 也变了!

ab 指向同一块内存,改一个等于改另一个。

链表操作的所有诡异 bug,90% 来源于没意识到"两个变量指向同一对象"。

这个认知贯穿后面所有题。


二、206. 反转链表

思路 :迭代,维护 headR(已反转部分的头)和 head(待处理部分的头)。

我犯的错

java 复制代码
// ❌ 错误写法
ListNode tmp = headR;
headR = head;
headR.next = tmp;
head = head.next; // 此时 head.next 已经是 tmp 了,不是原来的下一个!

headR = head 之后两个变量指向同一对象,修改 headR.next 同时也改了 head.next

正确写法:先把 next 存起来。

java 复制代码
public ListNode reverseList(ListNode head) {
    ListNode headR = null;
    while (head != null) {
        ListNode next = head.next; // 先存
        head.next = headR;
        headR = head;
        head = next;
    }
    return headR;
}

规律:凡是要改某个节点的 next,先把原来的 next 存下来。


三、234. 回文链表

思路:找中点 → 反转后半段 → 双指针对比。

我犯的两个错

错误1:计数从 1 开始

java 复制代码
int cnt = 1; // ❌ 应该是 0
while (true) {
    if (now == null) break;
    now = now.next;
    cnt++;
}

循环结束时 now == null,说明已经走完了,cnt 多加了 1。

错误2:用地址比较回文

java 复制代码
if (head != headR) return false; // ❌ 比较的是地址,不是值

反转后的节点是原节点,地址不可能相同,永远返回 false。

java 复制代码
if (head.val != headR.val) return false; // ✅

完整代码

java 复制代码
public boolean isPalindrome(ListNode head) {
    int cnt = 0;
    ListNode now = head;
    while (now != null) {
        now = now.next;
        cnt++;
    }
    int half = cnt / 2;
    ListNode headR = null;
    while (half > 0) {
        ListNode next = head.next;
        head.next = headR;
        headR = head;
        head = next;
        half--;
    }
    if (cnt % 2 == 1) head = head.next; // 奇数跳过中间节点
    while (head != null) {
        if (head.val != headR.val) return false;
        head = head.next;
        headR = headR.next;
    }
    return true;
}

四、141/142. 环形链表(检测 + 找入口)

141. 判断是否有环

思路:快慢指针,快指针每次走 2 步,慢指针走 1 步,有环必相遇。

我犯的错while 只在每轮开头检查一次条件,循环体中途不管。

java 复制代码
while (fut.next != null) {
    fut = fut.next;
    if (fut.next == now) return true; // fut 可能刚变成 null,取 .next 直接空指针
    fut = fut.next; // ❌ 没有判断 fut 是否为 null
}

中途 fut 变成 null 后,再执行 fut.next 就崩了。必须加中途检查:

java 复制代码
public boolean hasCycle(ListNode head) {
    if (head == null || head.next == null) return false;
    ListNode slow = head, fast = head;
    while (fast != null && fast.next != null) {
        slow = slow.next;
        fast = fast.next.next;
        if (slow == fast) return true;
    }
    return false;
}

whilefor 的条件都只管每轮入口,管不了循环体中途。

142. 找环的入口

数学结论:快慢指针相遇后,一个指针从 head 出发,另一个从相遇点出发,同速前进,再次相遇即为入口。

复制代码
设链表头到入口距离 = a
入口到相遇点距离 = b
相遇点回到入口距离 = c

快指针路程 = 2 × 慢指针路程
a + b + k(b+c) = 2(a + b)
→ a = c + (k-1)(b+c)

所以从 head 走 a 步 = 从相遇点走 c 步(加若干整圈),两者在入口相遇。

java 复制代码
public ListNode detectCycle(ListNode head) {
    ListNode slow = head, fast = head;
    while (fast != null && fast.next != null) {
        slow = slow.next;
        fast = fast.next.next;
        if (slow == fast) {
            ListNode ptr = head;
            while (ptr != slow) {
                ptr = ptr.next;
                slow = slow.next;
            }
            return ptr;
        }
    }
    return null;
}

五、160. 相交链表

思路:两个指针分别走完自己链表后走另一条,路程相同时必然在交点相遇(或同时到 null)。

关键 :相交判断用 ==(地址相同),不用 .val 比较。

复制代码
A 链表长度 a + c
B 链表长度 b + c
指针 pA 走完后走 B,总路程 = a + c + b
指针 pB 走完后走 A,总路程 = b + c + a
路程相等,在交点处地址相同。
java 复制代码
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
    ListNode pA = headA, pB = headB;
    while (pA != pB) {
        pA = pA == null ? headB : pA.next;
        pB = pB == null ? headA : pB.next;
    }
    return pA;
}

六、21. 合并两个有序链表

我的笨办法:while 循环每次比较两个头节点的值,把较小的接到结果链表上,最后再把剩余链表拼上去。逻辑没错,但要维护一个 dummy 头节点加三段循环,代码很啰嗦。

递归写法:换一个角度------合并两个链表,就是"选出当前最小的节点,它的 next 等于剩下两段继续合并的结果"。

复制代码
mergeTwoLists(l1, l2)
= 取 min(l1, l2),其 next = mergeTwoLists(另一个, min.next)

递归终止条件:某个链表为 null,直接返回另一个。

java 复制代码
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
    if (list1 == null) return list2;
    if (list2 == null) return list1;
    if (list1.val < list2.val) {
        list1.next = mergeTwoLists(list1.next, list2);
        return list1;
    } else {
        list2.next = mergeTwoLists(list1, list2.next);
        return list2;
    }
}

递归的本质:把"对整体的操作"转化成"对当前节点的操作 + 对剩余部分的同样操作"。链表天然适合递归,因为每个节点结构相同。

两种写法对比

复制代码
迭代:需要 dummy 头、三段循环、手动拼尾,代码 ~20 行
递归:终止条件 + 一个判断,代码 ~10 行,但有函数调用栈开销

七、错误总结

复制代码
┌─────────────────────────────────────────────────────┐
│  错误类型          │  出现题目  │  根因                │
├─────────────────────────────────────────────────────┤
│  修改 next 前没存  │  206/234   │  两变量同一对象      │
│  cnt 初始值为 1    │  234       │  循环边界差一        │
│  用 == 比较值      │  234       │  混淆地址和值比较    │
│  循环中途空指针    │  141       │  while 只管入口      │
└─────────────────────────────────────────────────────┘

链表题的本质是指针操作,核心心法只有一条:动 next 之前,先把它存起来。


下一篇:链表算法下篇------合并、排序、删除节点系列


相关推荐
信也科技布道师1 小时前
从Istio 503 NC 错误深入理解 Mesh 路由全链路原理
java·服务器·网络
大白话_NOI1 小时前
【洛谷 P2678】 [NOIP2015 提高组] 跳石头 超详细题解
c++·算法
swordbob1 小时前
3 大 I/O 模型BIO / NIO / AIO
java·linux·spring
xwz小王子1 小时前
ICRA 2026深度观察:全栈闭环成标配,中国具身智能势力显著崛起
大数据·人工智能·算法
Pluto_CSND1 小时前
Cron表达式使用说明
java
十五喵源码网1 小时前
基于SpringBoot2+vue2的酒店客房管理系统
java·毕业设计·springboot·论文笔记
孬甭_1 小时前
深入解析归并排序:稳定高效的分治典范
算法·排序算法
DXM05211 小时前
第14期|高阶分割模型:Transformer/SegFormer遥感应用
人工智能·python·神经网络·算法·计算机视觉·cnn·ageo
疯狂成瘾者1 小时前
Java 常用工具包 java.util
java·开发语言·windows