持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第21天,点击查看活动详情
题目链接:450. 删除二叉搜索树中的节点
题目描述
给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。
一般来说,删除节点可分为两个步骤:
- 首先找到需要删除的节点;
- 如果找到了,删除它。
进阶: 要求算法时间复杂度为 ,h 为树的高度。
提示:
- 节点数的范围 .
- 节点值唯一
root是合法的二叉搜索树
示例 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
输出: []
整理题意
题目给定一棵二叉搜索树的根节点 root,且给定一个二叉搜索树上的节点值 key,要求我们删除这个节点后保持剩下元素都在二叉搜索树上且保持性质不变,也就是仍然还是一棵二叉搜索树。返回删除节点后的二叉搜索树根节点。
解题思路分析
该题描述为一棵二叉搜索树,首先我们要对二叉搜索树有一定了解。
二叉搜索树有以下性质:
- 左子树的所有节点(如果有)的值均小于当前节点的值;
- 右子树的所有节点(如果有)的值均大于当前节点的值;
- 左子树和右子树均为二叉搜索树。
我们可以使用类似于二分的搜索方式来对 key 进行搜索,找到 key 所在的位置。
该题有多种思路可以解决,仅需明白其核心思路,剩下的只需暴力模拟,注意细节即可。
为了使得删除节点后保持剩下元素仍然构成一棵二叉搜索树,我们得想办法填补这个被删除得节点。考虑到被删除的节点左子树和右子树处于悬空状态,得找到一个节点来连接它们,显然可以直接让被删除节点父节点连接,但是存在问题,因为父节点只能再连接一个节点,所以这里可以考虑将左子树或者右子树连接上去,那么剩下的子树该如何处理呢,(我们假设剩下父节点连接被删节点的左子树,剩下右子树)由于右子树所有元素都大于左子树上的所有元素,所以我们可以找到左子树最大元素节点,连接在左子树最大元素节点的右子树上即可(因为是左子树最大元素节点,所以该节点上的右子树一定为空),同理,我们还可以将被删除节点的父亲节点连接右子树,再将剩下的左子树连接在右子树最小元素节点的左子树上。
此时我们可以注意到我们删除的节点其实是该节点左右子树的临界位置,也就是当前节点大于左子树上的所有元素,小于右子树上的所有元素,那么我们只需在其左右子树上再找到这样一个临界元素即可:
- 对于左子树来说,左子树上最大元素节点可以代替我们删除的节点元素;
- 对于右子树来说,右子树上最小元素节点可以代替我们删除的节点元素。 所以我们可以找删除节点左子树上最大元素或者右子树上最小元素来替换当前被删除元素的位置即可。
具体实现
- 二分查找当前需要删除的元素;
- 记录被删节点的父节点、左子树根节点、右子树根节点;
- 找到可以替换当前被删除节点的节点。
- 缝缝补补将二叉搜索树连接好即可。
- 返回当前的根节点即可。
需要注意的是:
- 被删除的节点可能是根节点;
- 被删除的节点可能是叶子节点;
- 被删除节点可能没有左子树;
- 被删除节点可能没有右子树。
复杂度分析
该题存在递归和迭代两种做法,在空间复杂度上有所差异。
递归:
- 时间复杂度:,其中
n为root的节点个数。最差情况下,寻找和删除节点各需要遍历一次树。 - 空间复杂度:,其中
n为root的节点个数。递归的深度最深为 。
迭代:
- 时间复杂度:,其中
n为root的节点个数。最差情况下,需要遍历一次树。 - 空间复杂度:,迭代仅需常数空间。
代码实现
代码实现部分为:将被删除节点的父亲节点连接被删除节点的右子树,再将左子树连接至右子树最小元素的左子树上。(其他情况的代码实现不做过多赘述)
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
private:
TreeNode *pre, *l, *r, *ans, *lp, *rp;
void dfsk(TreeNode* root, int key){
if(ans != NULL) return ;
if(root == NULL) return ;
if(root->val == key){
ans = root;
l = root->left;
r = root->right;
return ;
}
pre = root;
if(root->val > key) dfsk(root->left, key);
else dfsk(root->right, key);
}
void dfsl(TreeNode* root){
if(root == NULL) return ;
lp = root;
dfsl(root->left);
}
public:
TreeNode* deleteNode(TreeNode* root, int key) {
pre = l = r = ans = lp = rp = NULL;
dfsk(root, key);
if(ans == NULL) return root;
else{
//找到右子树的最左边的节点
dfsl(ans->right);
//删除root
if(pre == NULL){
//右子树为NULL
if(lp == NULL) return l;
//右子树不为NULL
else{
lp->left = l;
return ans->right;
}
}
//非root
else{
//如果在pre的左子树上
if(pre->val > key){
//ans没有右子树
if(lp == NULL) pre->left = ans->left;
//ans有右子树
else{
pre->left = ans->right;
lp->left = ans->left;
}
}
//如果在pre的右子树上
else{
if(lp == NULL) pre->right = ans->left;
else{
pre->right = ans->right;
lp->left = ans->left;
}
}
return root;
}
}
}
};
总结
- 该题核心在于考察码力,对树上的节点进行删除、插入操作;
- 同时考察对二叉搜索树的运用,递归和迭代的思想和使用。
- 该题细节较多,包括各种情况的判断以及处理,必要时候可以通过画图来进行理解。
- 测试结果:
结束语
台上一分钟,台下十年功,如果你期待在未来有所成就,就必须付出成倍的努力。无数光鲜亮丽的背后,都是日复一日、年复一年的坚持。今天的你,比别人多一点执着,就会多一点机会;比别人多一点坚持,就会多一点收获。