目录

题目描述

面试题 02.03. 删除中间节点

题意分析

这道题最反直觉的地方在于函数签名:只给一个待删节点 node,既没有 head,也没有返回值。这意味着无法从头遍历找前驱,也无法通过返回新头节点来表达结果——所有修改必须原地作用在传入的这条链表上,调用方从外部的 head 看过去必须已经生效。

常规删除靠的是「让前驱的 next 跳过自己」,而单链表节点没有指向前驱的指针,在只拿到 node 的情况下前驱是不可达的。所以题目实际上禁掉了标准删除路径,逼你换一个角度理解「删除」。

约束「待删除节点一定不是尾节点」是整道题的题眼。它保证了 node.next 必然存在,从而后继节点可以被安全访问。同时它也是这个解法的适用边界:如果允许删尾节点,任何只拿到 node 的方法都无解,因为前驱的 next 必须被改成 null,而前驱不可达。

还要注意题目对「删除」的验收标准是链表的值序列,而不是节点对象的内存地址。只要从 head 遍历得到的值序列里少了 node 原本的值、其余顺序不变,就算删对了,没人会检查哪个内存块被回收。这一点直接打开了下面的解法空间。

边界上:由于保证非尾节点,链表长度至少为 2,node 至少有一个后继;node 是否为头节点不影响做法,因为整个操作根本不碰前驱。

解法:复制后继节点并跳过

核心思路

常规删除需要前驱执行 prev.next = node.next,但题目只给待删节点,没有头节点,无法找到前驱。关键条件是 node 不是尾节点,因此它一定有后继。

不必真的移除 node 这个对象:先把后继节点的值复制到 node,再让 node.next 跳过后继。对从链表头遍历的调用者而言,原来位于 node 的值消失了,后面的值仍保持原顺序,效果与删除指定节点一致。

状态定义:把局部值序列写成 ... → v(node) → w(next) → rest。第一步后变为 ... → w(node) → w(next) → rest,第二步后变为 ... → w(node) → rest

正确性说明:操作前缀完全不变;node 改为承载后继值 w,再摘除原后继,因此结果只少了原值 vrest 的连接和顺序均未改变。所以最终值序列恰好等于删除 node 后的序列。该方法依赖“非尾节点”和题目只关心链表值序列这两个前提。

解题步骤

  • 读取 node.next.val,覆盖 node.val,使原待删值消失。
  • node.next 改为 node.next.next,摘除产生的重复后继节点。

例如 [4,5,1,9] 删除值为 5 的节点:复制后局部序列为 [4,1,1,9],跳过后继后得到 [4,1,9]。两行顺序不能交换,因为改完 next 后就无法再读取被跳过节点的值。若待删节点是尾节点,则仅凭当前节点无法修改其前驱,问题无解;题目已明确排除该情况。

代码实现

class Solution {
    public void deleteNode(ListNode node) {
        node.val = node.next.val;
        node.next = node.next.next;
    }
}
func deleteNode(node *ListNode) {
	node.Val = node.Next.Val
	node.Next = node.Next.Next
}

复杂度分析

  • 时间复杂度:O(1)。只进行两次局部赋值。
  • 空间复杂度:O(1)。不创建新节点或辅助容器。

关键点总结

  • 函数签名没有 head,说明常规“找前驱再删除”路径不可用。
  • 复制后继值并摘除后继,在题目的值序列语义下等价于删除当前节点。
  • “待删节点不是尾节点”是解法成立的必要条件,不是可忽略的边界说明。
  • 修改链表结构前先读取仍需使用的信息:先复制值,再改 next

易错点总结

  • 两行顺序写反[4,5,1,9] 删除 5 时,先跳过节点 1 再复制会得到 [4,9,9];若链表是 [1,2],还会空指针异常。
  • 只复制值、不跳过后继[4,5,1,9] 会变成 [4,1,1,9],节点数没有减少。
  • 只修改 next、不复制值:同一用例会变成 [4,5,9],实际删掉的是后继值 1。
  • 写成 node = node.next:这只改变函数内局部变量,前驱仍指向原节点,外部链表完全不变。
  • 试图给尾节点执行同样操作node.nextnull/nil,必然崩溃;没有 head 或前驱时无法删除尾节点。

相似题目

题目 难度 考察点
237. 删除链表中的节点 中等 与本题完全同构的另一编号版本,可用来验证同一套两行写法在不同签名下的迁移
203. 移除链表元素 简单 给了 head 且要按值批量删除,头节点可能被删,需要哑结点统一前驱
83. 删除排序链表中的重复元素 简单 有序数组上删重复只保留一个,靠单指针比较相邻值,不需要哑结点
82. 删除排序链表中的重复元素 II 中等 重复值要全部删光,必须用前驱指针加内层跳跃循环,边界比 83 复杂一层
面试题 02.01. 移除重复节点 简单 链表无序,需要哈希集合记录已出现的值,考察空间换时间与不用额外空间的取舍
19. 删除链表的倒数第 N 个结点 中等 删除位置由倒数下标给出,需要快慢指针拉开固定间距一趟定位