450. 删除二叉搜索树中的节点
文章目录
- [450. 删除二叉搜索树中的节点](https://leetcode.cn/problems/delete-node-in-a-bst/)
- 一、题目
- 二、题解
- 方法一:递归(一种麻烦的方法)
- 方法二:优化后的递归
一、题目
给定一个二叉搜索树的根节点 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
输出: []
提示:
- 节点数的范围
[0, 104]
. -105 <= Node.val <= 105
- 节点值唯一
root
是合法的二叉搜索树-105 <= key <= 105
进阶: 要求算法时间复杂度为 O(h),h 为树的高度。
二、题解
方法一:递归(一种麻烦的方法)
主要思路如下:
-
findNode
函数:这个函数用于在给定的二叉搜索树中找到值等于target
的节点。函数采用递归的方式,在树中搜索目标节点。如果当前节点为空,说明未找到目标节点,返回nullptr
。如果当前节点的值等于目标值,返回该节点。如果当前节点的值大于目标值,说明目标节点在左子树中,递归地搜索左子树。否则,目标节点在右子树中,递归地搜索右子树。 -
deleteNode
函数:这个函数用于删除二叉搜索树中值为key
的节点。首先,通过调用findNode
函数找到待删除的节点node
,同时维护一个指向node
的父节点pre
。然后根据删除情况进行不同的处理:- 如果
pre
为空,说明待删除节点是根节点。然后根据左右子树的情况进行调整,保留右子树并将左子树插入右子树中的最左叶子节点。 - 如果
pre
非空,根据pre
的位置判断node
是其父节点的左子节点还是右子节点。然后根据左右子树的情况进行调整,同样保留右子树并将左子树插入右子树中的最左叶子节点。
- 如果
最后,删除 node
节点并返回调整后的树。
/**
* 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 {
public:
TreeNode *pre = nullptr;
TreeNode* findNode(TreeNode *root, int target)
{
if(root == nullptr){
return root;
}
if(root->val == target){
return root;
}
pre = root;
if(root->val > target){
TreeNode *left = findNode(root->left, target);
return left;
}else{
TreeNode *right = findNode(root->right, target);
return right;
}
}
TreeNode* deleteNode(TreeNode* root, int key) {
TreeNode *node = findNode(root, key);
if(node == nullptr) return root;
if(pre == nullptr){
if(node->left && node->right){
TreeNode *temp = node->right;
while(temp->left){
temp = temp->left;
}
temp->left = node->left;
return node->right;
}else if(node->left){
return node->left;
}else if(node->right){
return node->right;
}else{
return nullptr;
}
}
if(pre && pre -> right == node){
if(node->left && node->right){
TreeNode *temp = node->right;
while(temp->left){
temp = temp->left;
}
temp->left = node->left;
pre->right = node->right;
}else if(node->left){
pre->right = node->left;
}else if(node->right){
pre->right = node->right;
}else{
pre->right = nullptr;
}
}
if(pre && pre->left == node){
if(node->left && node->right){
TreeNode *temp = node->right;
while(temp->left){
temp = temp->left;
}
temp->left = node->left;
pre->left = node->right;
}else if(node->left){
pre->left = node->left;
}else if(node->right){
pre->left = node->right;
}else{
pre->left = nullptr;
}
}
delete node;
return root;
}
};
方法二:优化后的递归
算法思路
-
递归搜索节点: 首先,我们从根节点开始递归地搜索目标节点(值为key的节点)。
- 如果当前节点为空,表示没有找到目标节点,直接返回空指针(nullptr)。
- 如果当前节点的值大于目标key,说明目标节点在左子树中,递归搜索左子树。
- 如果当前节点的值小于目标key,说明目标节点在右子树中,递归搜索右子树。
- 如果当前节点的值等于目标key,说明找到了目标节点,继续下一步。
-
处理删除操作: 一旦我们找到了目标节点,有几种情况需要处理:
- 如果目标节点没有左子树,那么我们可以用其右子树来替代这个节点,然后删除这个节点。
- 如果目标节点没有右子树,类似地,我们可以用其左子树来替代这个节点,然后删除这个节点。
- 如果目标节点既有左子树又有右子树,我们可以找到其右子树中最小的节点(即右子树中的最左节点,即后继节点),将该节点的值复制到目标节点上,然后递归地在右子树中删除这个后继节点。
-
返回根节点: 最后,无论如何都要返回当前子树的根节点。
具体实现
class Solution {
public:
TreeNode* deleteNode(TreeNode* root, int key) {
if (!root)
return nullptr;
if (root->val > key) {
root->left = deleteNode(root->left, key); // 递归搜索左子树
} else if (root->val < key) {
root->right = deleteNode(root->right, key); // 递归搜索右子树
} else {
if (!root->left) { // 没有左子树,用右子树替代
TreeNode* temp = root->right;
delete root;
return temp;
} else if (!root->right) { // 没有右子树,用左子树替代
TreeNode* temp = root->left;
delete root;
return temp;
}
TreeNode* temp = findMin(root->right); // 找到后继节点
root->val = temp->val;
root->right = deleteNode(root->right, temp->val); // 在右子树中删除后继节点
}
return root; // 返回根节点
}
private:
TreeNode* findMin(TreeNode* node) {
while (node->left)
node = node->left;
return node; // 找到最左节点,即后继节点
}
};
算法分析
- 在最坏情况下,我们需要遍历BST的高度h,即时间复杂度为O(h)。
- 递归深度取决于树的高度,所以空间复杂度也是O(h)。