450. 删除二叉搜索树中的节点
给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。
一般来说,删除节点可分为两个步骤:
- 首先找到需要删除的节点;
- 如果找到了,删除它。
说明: 要求算法时间复杂度为 O(h),h 为树的高度。
示例:
root = [5,3,6,2,4,null,7] key = 3 5 / \ 3 6 / \ \ 2 4 7 给定需要删除的节点值是 3,所以我们首先找到 3 这个节点,然后删除它。 一个正确的答案是 [5,4,6,2,null,null,7], 如下图所示。 5 / \ 4 6 / \ 2 7 另一个正确答案是 [5,2,6,null,4,null,7]。 5 / \ 2 6 \ \ 4 7
法1 递归
思路
先找到要删除的节点,再删除该节点。
查找:查找的时候利用二叉搜索树的性质。查找框架如下:
def deleteNode(self, root: TreeNode, key: int) -> TreeNode: # 找到了 if root.val == key: # 删除该节点 # 向右查找 elif root.val < key: # 返回删除后的右子树 root.right = self.deleteNode(root.right, key) # 向左查找 else: # 返回删除后的左子树 root.left = self.deleteNode(root.left, key) return root
删除:删除分为三种情况:
- 待删除节点为叶子节点:直接删除即可

- 待删除节点有一个子节点:将该子节点移到待删除节点位置

- 待删除节点拥有两个子节点:为了不破坏 BST 的性质,A必须找到左子树中最大的那个节点,或者右子树中最小的那个节点来接替自己。本题解采用后者

实现
采用递归的方式,站在某一棵子树根节点上(该子树会有父节点,但并不关心该子树根节点是在父节点左边还是右边),判断该根节点值与key的大小:
- 如果
a.val < key
, 说明待删除节点应该在右子树,那么此时返回完成删除操作后的右子树,并接到a
的右边,对于a
的父节点,返回a
即可

2. 如果
a.val > key
, 说明待删除节点应该在左子树,那么此时返回完成删除操作后的左子树,并接到a
的左边,对于a
的父节点,返回a
即可
3. 如果
a.val == key
,说明找到待删除的节点了,此时执行删除操作。- 如果待删除节点为叶子节点,那么直接返回None 即可

- 如果待删除节点存在一个子节点,那么直接将该子节点返回

- 如果待删除节点存在两个子节点,那么为了保证二叉搜索树的中序遍历递增性质,需要找左子树中最右节点或者右子树中最左节点来替换当前节点。分别成称为前驱和后继节点替换有两种策略
- 一种是只交换
val
,然后删除m
节点,如下图 - 首先令
m
节点的父节点指向m
的右子节点,此操作是把m节点架空了 - 其次将
m
节点左右子节点分别指向当前a
的左右节点 - 最后返回
m
节点给a
的父节点

2. 第二种是改变节点指向。
if a.val == key: parentNode = root minNode = root.right while minNode.left: parentNode = minNode minNode = minNode.left # 找到右子树中的最小节点了,且 parentNode 为最小节点的父节点 # 此时需要判断 最小节点是不是就是root的右子节点 if parentNode is not root: parentNode.left = minNode.right minNode.right = root.right minNode.left = root.left return minNode
题解
# Definition for a binary tree node. # class TreeNode: # def __init__(self, val=0, left=None, right=None): # self.val = val # self.left = left # self.right = right class Solution: def deleteNode(self, root: TreeNode, key: int) -> TreeNode: if root is None: return None # 找到了 if root.val == key: # 叶子节点 if root.left is None and root.right is None: return None # 有一个子节点 elif root.left is None or root.right is None: if root.left: return root.left else: return root.right # 有两个子节点 elif root.left and root.right: minNode = self.searchMinNode(root.right) # 实际应用中,应该是交换这两个节点的指针,而不能简单重置val root.val = minNode.val root.right = self.deleteNode(root.right, minNode.val) # 向右查找 elif root.val < key: root.right = self.deleteNode(root.right, key) # 向左查找 else: root.left = self.deleteNode(root.left, key) return root def searchMinNode(self, node): # 最左边的就是最小的 while node.left: node = node.left return node
上述代码是修改了待删除节点的val,且把替换的节点删掉了,而没有修改节点本身的地址。
以下代码是真实删除节点
# Definition for a binary tree node. # class TreeNode: # def __init__(self, val=0, left=None, right=None): # self.val = val # self.left = left # self.right = right class Solution: def deleteNode(self, root: TreeNode, key: int) -> TreeNode: if root is None: return if root.val > key: root.left = self.deleteNode(root.left, key) return root elif root.val < key: root.right = self.deleteNode(root.right, key) return root else: if root.left is None and root.right is None: return None elif root.left is None or root.right is None: if root.left: return root.left else: return root.right else: # 右子树中最小的节点 parentNode = root minNode = root.right while minNode.left: parentNode = minNode minNode = minNode.left # 找到右子树中的最小节点了,且 parentNode 为最小节点的父节点 # 此时需要判断 最小节点是不是就是root的右子节点 if parentNode is not root: parentNode.left = minNode.right minNode.right = root.right minNode.left = root.left return minNode def searchMinNode(self, node): while node.left: node = node.left return node
通俗易懂版本
# Definition for a binary tree node. # class TreeNode: # def __init__(self, val=0, left=None, right=None): # self.val = val # self.left = left # self.right = right class Solution: def deleteNode(self, root: Optional[TreeNode], key: int) -> Optional[TreeNode]: if root is None: return None if root.val < key: root.right = self.deleteNode(root.right, key) elif root.val > key: root.left = self.deleteNode(root.left, key) else: if root.left is None and root.right is None: return None elif root.left is None: return root.right elif root.right is None: return root.left else: # 找左子树的最大值 # 定位到左子树上 left_tree = root.left # 左子树没有右节点 if left_tree.right is None: left_tree.right = root.right return left_tree else: # 左子树有右节点 prev = left_tree cur = prev.right while cur and cur.right: prev = cur cur = cur.right prev.right = cur.left cur.right = root.right cur.left = left_tree return cur return root