Qoder平台集成Qwen3.8-Max-Preview模型:AI编程助手配置与实战指南
2026/7/23 3:34:21
目录
一、LeetCode 83 移除排序链表中的重复元素(保留一个)
题目核心
核心难点拆解
深度思路(盒子 - 标签 - 纸条模型)
代码实现
易踩坑点 & 底层原理
二、LeetCode 237 删除链表中的节点(无法访问头节点)
题目核心
核心难点拆解
深度思路(盒子 - 标签 - 纸条模型)
代码实现
易踩坑点 & 底层原理
三、LeetCode 82 删除排序链表中的重复元素 II(全删)
题目核心
核心难点拆解
深度思路(盒子 - 标签 - 纸条模型)
代码实现
易踩坑点 & 底层原理
四、跨题深度对比:链表删除的底层逻辑
通用核心原则(所有链表删除题的底层逻辑)
进阶思考
已排序链表中,重复元素仅保留一个(如1→1→1→2→3→3→1→2→3)。
cur和cur.next?若让cur初始指向head,比较cur.val == cur.next.val,会导致:cur.next为null;cur移动后无法回溯,连续重复节点删不干净(如1→1→1会剩最后一个 1,但中间的重复删不彻底)。dummy哑节点的底层价值:并非仅为 “避免删头节点”,而是提供一个永远非空的 “根锚点”,让cur可以稳定锚定 “已保留的最后一个不重复节点的前驱”,避免遍历过程中链表断裂。| 模型元素 | 角色与逻辑 |
|---|---|
| 盒子 | 链表节点实体(如 1、1、2),排序特性保证 “重复节点必连续”,无需跨区间检查; |
| 标签 | -dummy:贴在虚拟盒子(val=0)上,永久锚点,不移动;-cur:贴在 “已保留最后一个不重复节点的前驱盒子”(初始贴dummy),仅在无重复时后移; |
| 纸条 | 删除的本质是修改cur.next(纸条指向),跳过重复盒子;连续重复时cur不移动,持续修改纸条直到无重复。 |
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; } }cur.next != null→ 会访问cur.next.next导致空指针;cur→ 如1→1→1,cur 移到第一个 1 后,后续重复节点无法被跳过;仅给定待删除节点node(非尾节点),无链表头节点访问权限,要求删除该节点(如4→5→1→9删 5 →4→1→9)。
node节点?链表的节点删除本质是 “修改前驱节点的next”,但本题无表头,无法找到node的前驱节点;且链表节点是 “引用类型”,直接置空node仅会让当前标签失效,链表结构未变。val和next,而非节点本身的内存地址 —— 因此可以复用node的内存空间,替换其内容为下一个节点的内容,再删除下一个节点,等价于 “逻辑删除node”。node是尾节点,node.next为null,无法复制内容,此方法失效(尾节点删除必须依赖前驱节点)。| 模型元素 | 角色与逻辑 |
|---|---|
| 盒子 | 待删除盒子(如 5)、下一个盒子(如 1);复用待删除盒子的 “物理空间”,替换其 “内容”; |
| 标签 | 仅能访问node标签(贴在待删除盒子上),无其他锚点标签; |
| 纸条 | 先复制下一个盒子的val到当前盒子,再修改当前盒子的纸条(node.next),跳过下一个盒子; |
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; } }node = null→ 仅让当前标签失效,链表结构无变化(4→5→1→9 仍存在);node.next.val会空指针;已排序链表中,所有重复出现的元素全部删除,仅保留无重复的元素(如1→2→3→3→4→4→5→1→2→5;1→1→1→2→3→2→3)。
temp标签遍历重复区间?若仅用cur单次比较cur.next和cur.next.next,无法处理 “超过 2 个的连续重复”(如1→1→1),必须用temp走到重复区间的最后一个节点,才能精准跳过整个区间。cur.next != null && cur.next.next != null是 “基础防御”,temp != null && temp.next != null是 “区间遍历防御”,缺一不可 —— 否则会在重复区间末尾访问null.next导致空指针。| 模型元素 | 角色与逻辑 |
|---|---|
| 盒子 | 重复区间内的所有盒子需 “批量跳过”,无重复的盒子需保留; |
| 标签 | -dummy:永久根锚点;-cur:贴在 “待判断区间的前驱盒子”(初始贴dummy),仅在无重复时后移;-temp:遍历重复区间的临时标签,找到区间末尾; |
| 纸条 | cur.next直接指向重复区间的下一个盒子,批量删除整个区间的盒子; |
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; } }temp循环条件漏写temp != null→ 重复区间末尾temp.next为 null,temp.val空指针;cur.next = cur.next.next→ 仅跳过一个重复节点,无法处理多重复(如 1→1→1 仍会剩一个 1);| 维度 | 83 题(重复留一) | 237 题(指定节点删除) | 82 题(重复全删) |
|---|---|---|---|
| 核心策略 | 逐个跳过重复节点 | 偷梁换柱(内容替换) | 批量跳过重复区间 |
| 锚点依赖 | 依赖 dummy 做根锚点 | 无锚点(仅目标节点) | 依赖 dummy 做根锚点 |
| 空指针防御 | 双层循环条件 | 无(题目限定非尾节点) | 双层循环 + 区间遍历防御 |
| 核心难点 | 连续重复时 cur 不移动 | 理解 “节点逻辑删除” | 定位重复区间的首尾 |
| 底层原理 | 排序链表的连续性 | 链表节点的引用特性 | 排序链表的区间连续性 |
dummy哑节点做根锚点,避免链表断裂;cur/temp等临时标签移动,dummy/head等原始锚点仅做初始定位;next指向(逻辑删除);