在问 · 算法题解

450. 删除二叉搜索树中的节点

中等二叉树递归删除力扣原题 ↗

题目描述

输入二叉搜索树的根节点 root 和一个整数 key。树用层序数组表示,空位写 null。

若树中有值为 key 的节点,就把它删除,并调整结构使剩下的树依然满足二叉搜索树的性质;若没有,树保持不变。

返回操作后的根节点(根本身也可能被删掉而换成别的节点)。树为空时返回空。删除后的形状可能有多种,只要是合法的二叉搜索树且节点集合正确即可。

示例 1
输入:root = [5,3,6,2,4,null,7], key = 3
输出:[5,4,6,2,null,null,7]

解释:删除节点 3 后,用它的右子 4 顶替其位置,得到 [5,4,6,2,null,null,7]。

示例 2
输入:root = [5,3,6,2,4,null,7], key = 0
输出:[5,3,6,2,4,null,7]

解释:树中没有值为 0 的节点,原样返回。

示例 3
输入:root = [], key = 0
输出:[]

解释:空树中没有可删的节点,结果仍为空。

数据范围

  • 节点个数在 [0, 10^4] 范围内。
  • -10^5 <= Node.val <= 10^5
  • 所有节点值互不相同。
  • root 是合法的二叉搜索树。
  • -10^5 <= key <= 10^5

进阶:能否让时间复杂度为 O(h),其中 h 是树高?

解题思路:递归删除

先按 BST 性质递归查找:key 小就去左子树删,大就去右子树删,并把返回的新子树根接回来。找到后分三种情况:没有左孩子就返回右孩子;没有右孩子就返回左孩子;两个孩子都在,就找到右子树中最小的节点(后继),把它的值拷到当前节点,再到右子树里删除后继。

为什么正确

deleteNode(node, key) 返回的是从以 node 为根的 BST 中删去 key 后的新根,子树其余元素不变且仍满足 BST 性质。

复杂度分析

时间 O(h),空间 O(h),h 为树高。

易错点

一定要把递归返回值重新赋给 root.left 或 root.right,否则删除只在局部变量上生效。删除根节点时返回的新根可能是另一个节点。改写为后继值后,要删的是右子树中的后继值,而不是原来的 key。

参考代码

Python、Java、C++、Go、JavaScript、TypeScript 六种语言,逻辑一致。

class Solution:
    def deleteNode(self, root: Optional[TreeNode], key: int) -> Optional[TreeNode]:
        if not root:
            return None
        if key < root.val:
            root.left = self.deleteNode(root.left, key)
        elif key > root.val:
            root.right = self.deleteNode(root.right, key)
        else:
            if not root.left:
                return root.right
            if not root.right:
                return root.left
            succ = root.right
            while succ.left:
                succ = succ.left
            root.val = succ.val
            root.right = self.deleteNode(root.right, succ.val)
        return root

载入编辑器,自己跑一遍 →

其他解法

递归写法把“删除后返回新子树根”交给返回值,简洁;迭代写法省掉递归栈,但要手动维护父指针。

解法 2:迭代删除 时间 O(h),空间 O(1)

先沿 BST 迭代找到目标节点 cur 和它的父节点。若 cur 有两个孩子,找右子树最左的后继节点,把后继的值写入 cur,转为删除后继(后继没有左孩子)。此时要删的节点至多一个孩子,让父节点直接指向这个孩子即可;没有父节点时返回孩子作为新根。

只用常数额外空间,没有递归栈。需要手动记录父节点并判断是左孩子还是右孩子,分支较多。

class Solution:
    def deleteNode(self, root: Optional[TreeNode], key: int) -> Optional[TreeNode]:
        # 1. 迭代查找目标节点及其父节点
        parent, cur = None, root
        while cur and cur.val != key:
            parent = cur
            cur = cur.left if key < cur.val else cur.right
        if not cur:
            return root
        # 2. 有两个孩子:用后继的值覆盖,再改为删除后继(后继没有左孩子)
        if cur.left and cur.right:
            sp, succ = cur, cur.right
            while succ.left:
                sp, succ = succ, succ.left
            cur.val = succ.val
            parent, cur = sp, succ
        # 3. 现在 cur 至多一个孩子,用孩子顶替它
        child = cur.left if cur.left else cur.right
        if not parent:
            return child
        if parent.left is cur:
            parent.left = child
        else:
            parent.right = child
        return root

二叉树 专题的其他题目

查看 二叉树 全部题目 →