1.题目描述
450. 删除二叉搜索树中的节点
给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。
一般来说,删除节点可分为两个步骤:
首先找到需要删除的节点;
如果找到了,删除它。
示例 1:
输入:root = [5,3,6,2,4,null,7], key = 3
输出:[5,4,6,2,null,null,7]
解释:给定需要删除的节点值是 3,所以我们首先找到 3 这个节点,然后删除它。
一个正确的答案是 [5,4,6,2,null,null,7], 如下图所示。
另一个正确答案是 [5,2,6,null,4,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
输出: []
2.解题思路与代码
2.1 解题思路
题目要求在二叉搜索树中删除节点,并让删除后的二叉树仍然是二叉搜索树,那么就需要查找节点和删除节点两个步骤。
查找节点
由于是二叉搜索树,查找节点比较简单,可以选择使用迭代也可以选择使用递归的方式进行查找。这里是用递归进行解答,因为使用迭代的话需要维护查找节点的前一节点,并且需要判断查找到的节点是根结点、是前一节点左树还是右树等等,代码较为复杂,因此选用递归解答。在查找时根据二叉搜索树的性质,如果节点值大于要查找的值,则往左子树查找;如果节点值小于要查找的值,则往右子树查找。
删除节点
在查找到节点之后,便需要对该节点进行删除,删除有以下四种情况:
- 当前节点既没有左子树,也没有右子树
- 当前节点只有左子树,没有右子树
- 当前节点只有右子树,没有左子树
- 当前节点既有左子树,也有右子树
进行以上四种情况,进行对应的处理,curr 表示当前要删除的节点,parent 为当前节点的父节点:
-
当前节点既没有左子树,也没有右子树。此时删除当前节点,让父节点指向 null
-
当前节点只有左子树,没有右子树。此时只需让左子树移动到当前节点的位置,父节点引用指向左子树即可
-
当前节点只有右子树,没有左子树。此时只需让右子树移动到当前节点的位置,父节点引用指向右子树即可
-
当前节点既有左子树,也有右子树。这种情况比较复杂,首先让当前节点右子树上移到当前节点的位置,因为二叉搜索树的特性,做左子树小于右子树的最小值,因此在右子树上一直往左子树走,直到遇到最左边的叶节点,再将当前节点的左子树接在上面
2.2 代码
class Solution {
public TreeNode deleteNode(TreeNode root, int key) {
if (root == null) {
return null;
}
return process(root, key);
}
public TreeNode process(TreeNode root, int key) {
if (root == null) {
return null;
}
if (root.val < key) {
root.right = process(root.right, key);
} else if (root.val > key) {
root.left = process(root.left, key);
} else {
if (root.left == null && root.right == null) {
return null;
}
if (root.left == null) {
return root.right;
}
if (root.right == null) {
return root.left;
}
TreeNode curr = root.right;
while (curr.left != null) {
curr = curr.left;
}
curr.left = root.left;
root = root.right;
}
return root;
}
}
2.3 测试结果
通过测试
3.总结
- 这道题分为两步骤:查找节点和删除节点
- 查找结点使用递归查找更简单一点,使用迭代查找比较复杂
- 删除节点时需要根据删除节点的左右子树分四种情况进行处理