news 2026/10/18 1:30:39

LeetCode 237「删除链表中的节点」多语言解法全解析:只给节点不带头节点的 O(1) 删除技巧

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
LeetCode 237「删除链表中的节点」多语言解法全解析:只给节点不带头节点的 O(1) 删除技巧
  • 示例工程

【免费下载链接】leetcode

🔥LeetCode solutions in any programming language | 多种编程语言实现 LeetCode、《剑指 Offer(第 2 版)》、《程序员面试金典(第 6 版)》题解

项目地址:https://gitcode.com/doocs/leetcode
点击查看免费下载

LeetCode 237「删除链表中的节点」是一道非常经典的链表面试题:它不给你链表的头节点head,只给你一个待删除的节点node,却要求你完成删除操作。本文将基于 doocs/leetcode 仓库中该题的中文题解与多语言实现,深入拆解"节点赋值(值拷贝 + 跳过后继)"这一 O(1) 解法背后的数据结构原理,并给出 Python、Java、C++、Go、TypeScript、JavaScript、C#、C 八种语言的完整可运行代码,帮助你彻底掌握这一"无前驱删除"技巧,同时应对同类变体题型。

题目回顾:只给你节点,不给你 head

题目描述非常"反常规":

  • 有一个单链表的head,我们想删除其中的一个节点node;
  • 但无法访问第一个节点head,函数入参只有待删除的node本身;
  • 链表所有节点的值都是唯一的,且保证给定的node不是链表中的最后一个节点。

同时,题目对"删除"给出了明确的语义界定:

  • 给定节点的值不应该存在于链表中;
  • 链表中的节点数应该减少 1;
  • node前面所有值的顺序不变;
  • node后面所有值的顺序不变。

两个官方示例如下:

输入输出说明
head = [4,5,1,9],node = 5[4,1,9]删除值为 5 的第二个节点
head = [4,5,1,9],node = 1[4,5,9]删除值为 1 的第三个节点

约束条件(来自原题解文档):

  • 链表中节点的数目范围是[2, 1000];
  • -1000 <= Node.val <= 1000;
  • 链表中每个节点的值都是唯一的;
  • 需要删除的节点node是链表中的节点,且不是末尾节点。

在"自定义测试"中,判题系统会为你构建好整个链表并把node传入函数,输出则是调用函数之后的整个链表。

为什么这道题不能按常规套路删除

常规删除依赖"前驱节点"

在单向链表中,标准删除流程是:找到待删除节点的前驱节点pre,然后执行pre.next = pre.next.next,将前驱直接指向后继,从而把目标节点从链上摘除。

仓库中的 203. 移除链表元素 就是这种典型场景:题目给你head和val,解法维护一个哑节点dummy作为哨兵,用pre依次观察后继节点,命中目标值即执行pre.next = pre.next.next跳过它。这类解法的前提是能拿到头节点、能沿链遍历。

本题的"陷阱":没有前驱可用

而本题你只有node本身。单向链表的指针是单向的:node知道自己指向谁(node.next),却不知道"谁指向自己",因此无法修改前驱的next指针。如果沿用常规思路,这道题将无解。

这正是本题作为经典面试题的考察点:当"改前驱"这条路被堵死时,如何换一个角度完成等价的删除效果?

核心解法:节点赋值(值拷贝 + 跳过后继)

思路推导

原题解文档给出了简洁的思考过程:

只给定待删结点、没有前驱,无法改前驱的next。可将后继的值抄到当前结点,再跳过后继,效果等同删除。

具体而言,只需要两步赋值:

  1. node.val = node.next.val—— 把下一个节点的值拷贝进当前节点;
  2. node.next = node.next.next—— 让当前节点跳过后继节点,直接指向后继的后继。

执行完毕后,从调用者视角看:当前节点位置的值变成了原来后继的值,而原本后继的位置被"跳"出了链表,等价于"删除了传入的node"。节点数减少 1,前后节点的相对顺序均保持不变,完全满足题目语义。

以head = [4,5,1,9]、node = 5为例逐步推演:

  • 初始:4 -> 5 -> 1 -> 9,待删节点是值为 5 的第二个节点;
  • 第一步赋值:把后继(值为 1 的节点)的值抄给当前节点,节点变为4 -> 1 -> 1 -> 9;
  • 第二步跳过:当前节点的next指向后继的后继,链表变为4 -> 1 -> 9。

最终链表中不再存在值 5,节点数由 4 减为 3,前后顺序保持不变。

关于"删除"的内存语义

题目特别强调:"删除节点并不是指从内存中删除它。"因此本解法不需要调用free或delete释放内存。被跳过的那个节点虽然仍残留在堆上,但已经不再被链表引用:在 Python、Java、Go、C#、JavaScript/TypeScript 等带垃圾回收(GC)的运行时中,它会由 GC 自动回收;在 C/C++ 中,LeetCode 判题环境关注的是链表逻辑结构的变化,函数内无需手动释放内存。这也解释了为什么仓库中 Solution.c 的实现只做两步指针操作而不含free调用。

为什么题目约束"恰好"保证了该解法的正确性

  • 保证node不是尾节点:这是本解法成立的前提。若node是尾节点,则node.next为null,第一步取值就会空指针异常,更无从谈"跳过后继";
  • 保证节点值唯一:第一步的"值拷贝"不会产生值冲突,确保删除后链表中确实不再存在该值,逻辑语义与"真正删除"一致;
  • 节点数范围[2, 1000]:保证链表中至少有两个节点,即任何待删节点都存在合法的后继。

多语言实现(完整可运行代码)

以下实现均来自仓库 0237.Delete Node in a Linked List 目录下的Solution.*源文件与题解文档,函数签名完全贴合 LeetCode 平台要求:不返回任何值,原地(in-place)修改链表。

Python3

# Definition for singly-linked list. # class ListNode: # def __init__(self, x): # self.val = x # self.next = None class Solution: def deleteNode(self, node): """ :type node: ListNode :rtype: void Do not return anything, modify node in-place instead. """ node.val = node.next.val node.next = node.next.next

Java

/** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode(int x) { val = x; } * } */ class Solution { public void deleteNode(ListNode node) { node.val = node.next.val; node.next = node.next.next; } }

C++

/** * Definition for singly-linked list. * struct ListNode { * int val; * ListNode *next; * ListNode(int x) : val(x), next(NULL) {} * }; */ class Solution { public: void deleteNode(ListNode* node) { node->val = node->next->val; node->next = node->next->next; } };

Go

/** * Definition for singly-linked list. * type ListNode struct { * Val int * Next *ListNode * } */ func deleteNode(node *ListNode) { node.Val = node.Next.Val node.Next = node.Next.Next }

TypeScript

/** * Definition for singly-linked list. * class ListNode { * val: number * next: ListNode | null * constructor(val?: number, next?: ListNode | null) { * this.val = (val===undefined ? 0 : val) * this.next = (next===undefined ? null : next) * } * } */ /** Do not return anything, modify it in-place instead. */ function deleteNode(node: ListNode | null): void { node.val = node.next.val; node.next = node.next.next; }

JavaScript

/** * Definition for singly-linked list. * function ListNode(val) { * this.val = val; * this.next = null; * } */ /** * @param {ListNode} node * @return {void} Do not return anything, modify node in-place instead. */ var deleteNode = function (node) { node.val = node.next.val; node.next = node.next.next; };

C#

/** * Definition for singly-linked list. * public class ListNode { * public int val; * public ListNode next; * public ListNode(int x) { val = x; } * } */ public class Solution { public void DeleteNode(ListNode node) { node.val = node.next.val; node.next = node.next.next; } }

C

/** * Definition for singly-linked list. * struct ListNode { * int val; * struct ListNode *next; * }; */ void deleteNode(struct ListNode* node) { node->val = node->next->val; node->next = node->next->next; }

可以看到,无论哪种语言,核心逻辑都严格收敛为同一条语句模式:先拷贝后继值,再跳过后继节点。这也是仓库中 Solution.py、Solution.java 等全部 8 份源码文件与题解文档保持一致的原因。

边界条件与注意事项

  1. node必须是链表中真实存在的节点:题目保证传入的node在链表中,因此无需做存在性校验;
  2. node绝不能是尾节点:node.next为null时,node.next.val会抛出空指针异常(Java/C++/C#/Go/TS)或AttributeError(Python);正是因为题目保证了这一点,代码才可以"裸奔"式直接访问node.next,无需判空;
  3. 值唯一性是语义前提:若存在重复值,第一步"拷贝覆盖"可能让"删除后链表中不存在该值"这一要求无法严格满足;
  4. 不要试图获取head:函数签名中根本没有head参数,不要在实现里尝试遍历链表定位前驱——那既不可行也不必要;
  5. 注意指针的求值顺序:在 C/C++/C#/Java 中,node.val = node.next.val与node.next = node.next.next两行顺序不可颠倒。必须先拷贝值,再改写next;若先改写next,则后继指针已丢失,第一步就无从拷贝。Python/Go/JS/TS 的实现同样遵循"先取值、再跳链"的顺序。

复杂度分析

  • 时间复杂度:O(1)。整个删除过程只执行常数次赋值与指针操作,与链表长度无关,无需遍历;
  • 空间复杂度:O(1)。只使用现有节点和指针,不申请任何额外数据结构。

这一"常数时间删除"是本题的核心亮点:常规的单链表删除需要 O(n) 遍历找前驱,而本题借助"值拷贝"这一技巧,在无法访问前驱的限制下依然做到了 O(1)。

同类题型拓展:面试题 02.03「删除中间节点」

同样的解题模式在仓库的《程序员面试金典》部分也有收录:面试题 02.03. 删除中间节点。该题给定单向链表a->b->c->d->e->f中的中间节点c,要求将其删除后链表变为a->b->d->e->f——入参同样只有待删节点本身,同样保证不是尾节点,解法与本题完全一致:

class Solution: def deleteNode(self, node): node.val = node.next.val node.next = node.next.next

可见"无前驱删除"是链表题中的高频考察点。掌握了 LeetCode 237 的节点赋值法,遇到这类变体可以直接迁移复用。与之形成对比的是前面提到的 203. 移除链表元素:当你能拿到head时,应优先使用哑节点 + 前驱遍历的标准删除法;而当入参只有节点本身时,则应切换到"值拷贝 + 跳过后继"这一非常规但极其优雅的技巧。

小结

  • LeetCode 237 的独特之处在于只给待删节点、不给头节点,导致常规的"找前驱、改指针"方案不可用;
  • 解法核心是节点赋值:把后继的值拷贝到当前节点,再把当前节点的next指向后继的后继,两步完成 O(1) 删除;
  • 该解法成立依赖两个关键前提:node非尾节点与节点值唯一,二者均由题目约束保证;
  • 仓库中该题收录了 Solution.py、Solution.java、Solution.cpp、Solution.go、Solution.ts、Solution.js、Solution.cs、Solution.c 共 8 种语言的完整实现,并配中文与英文题解,可作为刷题与面试复习的可靠参考。
  • 示例工程

【免费下载链接】leetcode

🔥LeetCode solutions in any programming language | 多种编程语言实现 LeetCode、《剑指 Offer(第 2 版)》、《程序员面试金典(第 6 版)》题解

项目地址:https://gitcode.com/doocs/leetcode
点击查看免费下载

相关推荐

上一篇:3步搞定AmberELEC BIOS配置:让复古游戏完美运行
下一篇:DeFlock地图秒开揭秘:ALPR数据经纬度分片加载与缓存设计

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/18 1:29:54

如何用 gdsdecomp 从 PCK/APK/EXE 中恢复一个完整 Godot 游戏项目

如何用 gdsdecomp 从 PCK/APK/EXE 中恢复一个完整 Godot 游戏项目 【免费下载链接】gdsdecomp Godot reverse engineering tools 项目地址: https://gitcode.com/GitHub_Trending/gd/gdsdecomp 手里只剩一个发布版游戏的目录&#xff0c;.gdc 脚本、.res 场景全是二进制…

作者头像 李华
网站建设 2026/10/18 1:29:36

DB2异机恢复实战:NetBackup配置、恢复命令与避坑指南

简介&#xff1a;这份文档面向使用 NetBackup 实现 DB2 数据库异机恢复的运维与 DBA 人员&#xff0c;聚焦备份代理配置、参数调整与恢复策略落地&#xff0c;适合具备一定 DB2 与 NBU 基础、需要搭建异机恢复环境的中高级技术人员参考。资源包内共 1 个 doc 文件&#xff0c;压…

作者头像 李华
网站建设 2026/10/18 1:25:44

储能锂离子电池组热管理结构设计:从风冷到液冷的温差控制实践

简介&#xff1a;这份PDF文档围绕储能用锂离子电池组的热管理结构设计展开&#xff0c;面向电池热管理、储能系统及仿真分析相关工程师与研究人员&#xff0c;针对电池密集排列下散热不均、温差过大带来的寿命与安全隐患&#xff0c;给出了风冷式热管理设计思路。作者以磷酸铁锂…

作者头像 李华
网站建设 2026/10/18 1:25:16

GT911触摸芯片双I2C地址详解:上电时序与驱动配置

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华