LeetCode 83/237/82 链表删除问题-盒子模型

目录

[一、LeetCode 83 移除排序链表中的重复元素(保留一个)](#一、LeetCode 83 移除排序链表中的重复元素(保留一个))

题目核心

核心难点拆解

[深度思路(盒子 - 标签 - 纸条模型)](#深度思路(盒子 - 标签 - 纸条模型))

代码实现

[易踩坑点 & 底层原理](#易踩坑点 & 底层原理)

[二、LeetCode 237 删除链表中的节点(无法访问头节点)](#二、LeetCode 237 删除链表中的节点(无法访问头节点))

题目核心

核心难点拆解

[深度思路(盒子 - 标签 - 纸条模型)](#深度思路(盒子 - 标签 - 纸条模型))

代码实现

[易踩坑点 & 底层原理](#易踩坑点 & 底层原理)

[三、LeetCode 82 删除排序链表中的重复元素 II(全删)](#三、LeetCode 82 删除排序链表中的重复元素 II(全删))

题目核心

核心难点拆解

[深度思路(盒子 - 标签 - 纸条模型)](#深度思路(盒子 - 标签 - 纸条模型))

代码实现

[易踩坑点 & 底层原理](#易踩坑点 & 底层原理)

四、跨题深度对比:链表删除的底层逻辑

通用核心原则(所有链表删除题的底层逻辑)

进阶思考


一、LeetCode 83 移除排序链表中的重复元素(保留一个)

题目核心

已排序链表中,重复元素仅保留一个(如 1→1→1→2→3→31→2→3)。

核心难点拆解

  1. 为什么不能直接比较 curcur.next 若让 cur 初始指向 head,比较 cur.val == cur.next.val,会导致:
    • 空指针风险:当链表只剩最后一个节点时,cur.nextnull
    • 锚点丢失:cur 移动后无法回溯,连续重复节点删不干净(如 1→1→1 会剩最后一个 1,但中间的重复删不彻底)。
  2. dummy 哑节点的底层价值 :并非仅为 "避免删头节点",而是提供一个永远非空的 "根锚点" ,让 cur 可以稳定锚定 "已保留的最后一个不重复节点的前驱",避免遍历过程中链表断裂。

深度思路(盒子 - 标签 - 纸条模型)

模型元素 角色与逻辑
盒子 链表节点实体(如 1、1、2),排序特性保证 "重复节点必连续",无需跨区间检查;
标签 - dummy:贴在虚拟盒子(val=0)上,永久锚点,不移动;- cur:贴在 "已保留最后一个不重复节点的前驱盒子"(初始贴 dummy),仅在无重复时后移;
纸条 删除的本质是修改 cur.next(纸条指向),跳过重复盒子;连续重复时 cur 不移动,持续修改纸条直到无重复。

代码实现

java 复制代码
class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        // 关键点1:空链表防御(基础边界,所有链表题必加)
        if (head == null) return null;

        // 关键点2:dummy哑节点------根锚点,避免头节点重复时的边界问题
        ListNode dummy = new ListNode(0, head);
        ListNode cur = dummy; // cur锚定"已保留节点的前驱",核心标签

        // 关键点3:循环条件双层防御------避免cur.next或cur.next.next为null时的空指针
        while (cur.next != null && cur.next.next != null) {
            if (cur.next.val == cur.next.next.val) {
                // 难点1:连续重复时,cur不移动,仅修改纸条跳过重复节点
                // 例:1→1→1,第一次跳过第二个1,cur仍在dummy,第二次跳过第三个1
                cur.next = cur.next.next;
            } else {
                // 无重复时,cur才后移------保证cur始终锚定"有效前驱"
                cur = cur.next;
            }
        }
        // 关键点4:返回dummy.next而非head------head可能已被跳过(如链表全重复时)
        return dummy.next;
    }
}

易踩坑点 & 底层原理

  • 坑 1:循环条件只写 cur.next != null → 会访问 cur.next.next 导致空指针;
  • 坑 2:连续重复时移动 cur → 如 1→1→1,cur 移到第一个 1 后,后续重复节点无法被跳过;
  • 原理:排序链表的重复是 "连续的",因此只需 "原地跳过",无需额外存储 / 回溯。

二、LeetCode 237 删除链表中的节点(无法访问头节点)

题目核心

仅给定待删除节点 node(非尾节点),无链表头节点访问权限,要求删除该节点(如 4→5→1→9 删 5 → 4→1→9)。

核心难点拆解

  1. 为什么不能直接删除 node 节点? 链表的节点删除本质是 "修改前驱节点的 next",但本题无表头,无法找到 node 的前驱节点;且链表节点是 "引用类型",直接置空 node 仅会让当前标签失效,链表结构未变。
  2. "偷梁换柱" 的底层逻辑 :链表的 "节点价值" 在于 valnext,而非节点本身的内存地址 ------ 因此可以复用 node 的内存空间,替换其内容为下一个节点的内容,再删除下一个节点,等价于 "逻辑删除 node"。
  3. 为什么题目限定 "非尾节点"?node 是尾节点,node.nextnull,无法复制内容,此方法失效(尾节点删除必须依赖前驱节点)。

深度思路(盒子 - 标签 - 纸条模型)

模型元素 角色与逻辑
盒子 待删除盒子(如 5)、下一个盒子(如 1);复用待删除盒子的 "物理空间",替换其 "内容";
标签 仅能访问 node 标签(贴在待删除盒子上),无其他锚点标签;
纸条 先复制下一个盒子的 val 到当前盒子,再修改当前盒子的纸条(node.next),跳过下一个盒子;

代码实现

java 复制代码
class Solution {
    public void deleteNode(ListNode node) {
        // 关键点1:复制下一个盒子的内容到当前盒子------核心逻辑,偷梁换柱
        // 例:node是5,node.next是1 → node.val = 1,此时链表变为4→1→1→9
        node.val = node.next.val;

        // 关键点2:修改纸条,跳过下一个盒子------删除"被复制的下一个盒子"
        // 例:node.next = 1.next = 9,最终链表4→1→9,等价于删除了原5节点
        node.next = node.next.next;
    }
}

易踩坑点 & 底层原理

  • 坑 1:试图直接 node = null → 仅让当前标签失效,链表结构无变化(4→5→1→9 仍存在);
  • 坑 2:忽略 "非尾节点" 限制 → 若 node 是尾节点,node.next.val 会空指针;
  • 原理:链表的 "节点标识" 是逻辑上的(val+next),而非物理上的(内存地址),这是 "偷梁换柱" 能成立的核心。

三、LeetCode 82 删除排序链表中的重复元素 II(全删)

题目核心

已排序链表中,所有重复出现的元素全部删除 ,仅保留无重复的元素(如 1→2→3→3→4→4→51→2→51→1→1→2→32→3)。

核心难点拆解

  1. 与 83 题的核心差异:83 题是 "保留一个重复元素",只需 "逐个跳过";82 题是 "全删重复元素",需 "定位重复区间的首尾,批量跳过整个区间"。
  2. 为什么需要 temp 标签遍历重复区间? 若仅用 cur 单次比较 cur.nextcur.next.next,无法处理 "超过 2 个的连续重复"(如 1→1→1),必须用 temp 走到重复区间的最后一个节点,才能精准跳过整个区间。
  3. 循环条件的多层防御cur.next != null && cur.next.next != null 是 "基础防御",temp != null && temp.next != null 是 "区间遍历防御",缺一不可 ------ 否则会在重复区间末尾访问 null.next 导致空指针。

深度思路(盒子 - 标签 - 纸条模型)

模型元素 角色与逻辑
盒子 重复区间内的所有盒子需 "批量跳过",无重复的盒子需保留;
标签 - dummy:永久根锚点;- cur:贴在 "待判断区间的前驱盒子"(初始贴 dummy),仅在无重复时后移;- temp:遍历重复区间的临时标签,找到区间末尾;
纸条 cur.next 直接指向重复区间的下一个盒子,批量删除整个区间的盒子;

代码实现

java 复制代码
class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        // 关键点1:双层边界防御------空链表/单节点链表直接返回
        if (head == null || head.next == null) return head;

        // 关键点2:dummy哑节点------避免头节点全重复时的边界问题(如1→1→2)
        ListNode dummy = new ListNode(0, head);
        ListNode cur = dummy; // cur锚定"待判断区间的前驱"

        while (cur.next != null && cur.next.next != null) {
            // 发现重复区间的起点
            if (cur.next.val == cur.next.next.val) {
                // 关键点3:temp标签遍历重复区间,找到区间最后一个节点
                ListNode temp = cur.next;
                // 区间遍历防御:temp != null 避免temp.next空指针
                while (temp != null && temp.next != null && temp.val == temp.next.val) {
                    temp = temp.next; // 走到重复区间的最后一个节点
                }
                // 关键点4:批量跳过整个重复区间------核心逻辑
                // 例:1→1→1→2,temp走到第三个1,cur.next = 2,直接跳过所有1
                cur.next = temp.next;
            } else {
                // 无重复时,cur才后移------保证cur始终锚定"有效前驱"
                cur = cur.next;
            }
        }
        return dummy.next;
    }
}

易踩坑点 & 底层原理

  • 坑 1:temp 循环条件漏写 temp != null → 重复区间末尾 temp.next 为 null,temp.val 空指针;
  • 坑 2:找到重复区间后直接 cur.next = cur.next.next → 仅跳过一个重复节点,无法处理多重复(如 1→1→1 仍会剩一个 1);
  • 原理:排序链表的重复区间是 "连续的",因此只需一次遍历找到区间末尾,即可批量删除,时间复杂度仍为 O (n)。

四、跨题深度对比:链表删除的底层逻辑

维度 83 题(重复留一) 237 题(指定节点删除) 82 题(重复全删)
核心策略 逐个跳过重复节点 偷梁换柱(内容替换) 批量跳过重复区间
锚点依赖 依赖 dummy 做根锚点 无锚点(仅目标节点) 依赖 dummy 做根锚点
空指针防御 双层循环条件 无(题目限定非尾节点) 双层循环 + 区间遍历防御
核心难点 连续重复时 cur 不移动 理解 "节点逻辑删除" 定位重复区间的首尾
底层原理 排序链表的连续性 链表节点的引用特性 排序链表的区间连续性

通用核心原则(所有链表删除题的底层逻辑)

  1. 锚点优先 :只要涉及 "删除头节点" 或 "前驱节点不可知",优先用 dummy 哑节点做根锚点,避免链表断裂;
  2. 标签不碰原始锚点 :始终用 cur/temp 等临时标签移动,dummy/head 等原始锚点仅做初始定位;
  3. 删除本质是改纸条 :所有删除操作均不销毁节点(物理删除),而是修改 next 指向(逻辑删除);
  4. 边界防御前置:所有链表题先处理 "空链表 / 单节点链表",避免后续循环的空指针。

进阶思考(深度延伸)

  • 若链表未排序,83/82 题该如何修改?→ 需要用哈希表记录已出现的 val,遍历链表时跳过重复值(时间 O (n),空间 O (n));
  • 237 题若允许删除尾节点,该如何处理?→ 必须遍历链表找到尾节点的前驱(时间 O (n)),或改用双向链表(空间换时间)。

相关推荐
小虎牙0077 小时前
RSA 的核心原理
算法
重生之后端学习7 小时前
56. 合并区间
java·数据结构·后端·算法·leetcode·职场和发展
小猪猪屁7 小时前
顺序表与链表:头插法与尾插法详解
c语言·数据结构·c++
历程里程碑7 小时前
C++ 5:模板初阶
c语言·开发语言·数据结构·c++·算法
leoufung7 小时前
LeetCode 74. Search a 2D Matrix
数据结构·算法·leetcode
Kiri霧7 小时前
Go数据类型介绍
java·算法·golang
Mxsoft6198 小时前
AR远程定位偏差救场!某次现场故障,SLAM算法精准对齐设备模型!
算法·ar
liu****8 小时前
一.脚手架介绍以及部分工具使用
开发语言·数据结构·c++·手脚架开发