← 返回题目列表

删除指定值叶子节点时,为什么删除后父节点可能也会变成叶子?

中等 第 26 / 30 题 更新于 2026/07/30
二叉树删除叶子后序遍历递归

简化版

删除指定值叶子节点要用后序遍历:先递归处理左右子树,再判断当前节点是否变成目标值叶子。因为孩子删掉后,父节点可能从非叶子变成叶子,也需要继续删除。

详细版

这题不能只扫描一次原始叶子。比如父节点值也是 target,它原本有一个 target 叶子孩子;孩子删除后,父节点变成叶子,也应该被删除。

正确思路:

  • 递归处理左子树,更新 node.left
  • 递归处理右子树,更新 node.right
  • 如果当前节点左右都为空,并且值等于 target,返回 null。
  • 否则返回当前节点。

后序遍历是关键,因为当前节点是否是叶子,要在孩子删除之后才能判断。

完整版教学

一、这题为什么不是普通删除

普通“删除所有值等于 target 的叶子”听起来像找叶子即可。但删除会改变树结构。一个节点原本不是叶子,可能因为孩子被删光而变成新叶子。

例子:

    1
   /
  2
 /
2
target = 2

最底下的 2 先删除后,中间的 2 变成叶子,也要删除。最后根 1 保留。

二、为什么必须后序

当前节点是否该删,取决于处理完左右子树后的状态。如果先判断当前节点,再处理孩子,就会错过“删除孩子后变成叶子”的情况。

后序顺序是:

处理左子树
处理右子树
处理当前节点

这个顺序符合依赖关系。父节点要等孩子的最终结果出来,才能判断自己是不是叶子。

三、递归返回值代表什么

递归函数可以返回处理后的子树根。返回 null 表示这棵子树被删掉;返回 node 表示保留。

node.left = remove(node.left, target);
node.right = remove(node.right, target);
if (!node.left && !node.right && node.val === target) return null;
return node;

这段代码的关键是把递归结果重新接回父节点。只调用递归但不赋值,父节点仍然指向旧孩子。

四、为什么根节点也可能被删除

如果整棵树最后只剩一个 target 根节点,根也应该删除。递归返回值天然支持这一点:最终函数返回 null,表示整棵树被删空。

单节点 target
remove(root) -> null

如果你只在父节点里删除孩子,就处理不了根节点,因为根没有父节点。返回新根是树修改题的常用技巧。

五、和普通剪枝题的关系

这题属于树剪枝。剪枝类问题一般都适合后序,因为是否保留当前节点常依赖子树处理结果。

问题为什么后序
删除目标叶子孩子删后父可能变叶子
删除全 0 子树要知道子树是否全 0
修剪路径和不足要知道左右路径是否有效

一看到“删除后还可能继续影响父节点”,就要想到后序。

六、复杂度和边界

每个节点最多访问一次,时间复杂度 O(n)。递归栈空间和树高有关,最坏退化链是 O(n),平衡树是 O(log n)。

记忆钩子:树剪枝先问“父节点判断是否依赖孩子结果”;如果依赖,就十有八九用后序。

七、常见误区与追问

  • 误区:只删除原始叶子就够了。 删除会让父节点变成新叶子,可能还要继续删。
  • 误区:先判断当前节点再递归孩子。 当前是否叶子必须在孩子处理后判断。
  • 误区:递归结果不用接回去。 必须 node.left = ...,否则删除结果不会反映到父节点。
  • 追问:根节点能被删除吗? 能,最终返回 null 即表示整棵树为空。
  • 追问:复杂度是多少? 时间 O(n),递归栈空间 O(h)。

八、加强记忆

删除目标叶子的核心不是“找叶子”,而是“删除会产生新叶子”。后序遍历让孩子先完成变化,父节点再根据新状态决定去留。递归返回处理后的根,才能自然覆盖根节点被删的情况。