1. 问题描述:
给定一个二叉搜索树的根节点 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
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/delete-node-in-a-bst
2. 思路分析:
这是一道经典的删除二叉搜索树节点的题目(删除节点的过程都是一些固定的步骤),首先是需要找到要删除节点的位置,因为是二叉搜索树,我们在查找删除节点的时候可以根据当前根节点与key之间的大小关系决定递归左子树还是右子树,当key大于根节点时说明删除的节点可能在右侧,小于的时候说明删除的节点可能在左侧,等于的时候说明当前根节点就是我们要删除的节点(查找节点的过程其实是一个二分的过程),对于删除的节点我们分三种情况讨论即可(删除节点的过程其实是一个递归的过程):
- 删除的节点是叶子节点,直接删除叶子节点,直接删除叶子节点不会对二叉搜索树的特性有影响。
- 删除的节点存在左子树或者是右子树,我们可以直接将左子树或者是右子树提上去即可,也即将当前当前的左子树或者是右子树覆盖当前删除的节点。
- 删除的节点存在左右子树,这个时候我们需要找到当前删除节点的前驱或者是后继(中序遍历的前驱或者是后继)下面以后继为例,当前删除节点的后继是右子树中最靠左的节点,后继一定是没有左孩子的,所以一定可以转变为第一或者是第二种的情况,找到后继节点之后,将后继节点的值覆盖到当前删除节点的值,然后递归删除掉后继节点即可。
因为使用的python语言,不像c++语言可以在方法中可以传递一个引用直接修改,这里需要注意的一个坑是我们在修改完待删除节点返回到上一层的时候需要修改当前节点的左子树或者是右子树这样才能够真正删除节点(也即修改root的左子树或者是右子树),递归的是哪一边那么我们修改哪一边的子树。
3. 代码如下:
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
class Solution:
def delete(self, root: TreeNode, key: int):
if not root: return
if key > root.val:
root.right = self.delete(root.right, key)
elif key < root.val:
root.left = self.delete(root.left, key)
else:
# 找到了删除的节点, 分情况讨论
# 为叶子节点
if not root.left and not root.right:
root = None
# 没有左孩子
elif not root.left:
root = root.right
# 没有右孩子
elif not root.right:
root = root.left
else:
# 这里是找的是后继, 前驱也行
p = root.right
while p.left:
p = p.left
# 将后继节点的值覆盖到当前删除的节点上, 递归删除后继节点(删除的后继节点一定属于叶子节点或者是只存在左子树或者是右子树)
root.val = p.val
root.right = self.delete(root.right, p.val)
return root
# 分情况讨论递归删除节点即可
def deleteNode(self, root: TreeNode, key: int) -> TreeNode:
return self.delete(root, key)