题目32:450. 删除二叉搜索树中的节点
同类题目
前置知识
二叉搜索树(Binary Search Tree) :也叫做二叉查找树、有序二叉树或者排序二叉树。是指一棵空树或者具有下列性质的二叉树:
- 如果任意节点的左子树不为空,则左子树上所有节点的值均小于它的根节点的值。
- 如果任意节点的右子树不为空,则右子树上所有节点的值均大于它的根节点的值。
- 任意节点的左子树、右子树均为二叉搜索树。
题目描述
给定一个二叉搜索树的根节点 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 为树的高度。
思路分析
递归
如何在搜索二叉树中删除一个节点?
- 若被删除节点的左子树为空,使用右子树代替被删除节点的位置。(这个情况包括了左右子树为空的情况)
- 若被删除节点的右子树为空,使用左子树代替被删除节点的位置。
- 若被删除节点的左右子树均不为空,则根据二叉搜索树的中序遍历有序性,删除该节点时,可以使用其直接前驱(或直接后继)代替被删除节点的位置。
直接前驱:在中序遍历中,节点p 的直接前驱为其左子树的最右侧的叶子节点。
直接后继:在中序遍历中,节点p的直接后继为其右子树的最左侧的叶子节点。
程序代码
方法一:
// https://leetcode.cn/problems/delete-node-in-a-bst/
// 450. 删除二叉搜索树中的节点
#include<iostream>
#include<vector>
using namespace std;
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* deleteNode(TreeNode* root, int key) {
if(root == nullptr)
{
return nullptr;
}
// 寻找节点
if(key > root->val)
{
root->right = deleteNode(root->right,key);
}else if(key < root->val)
{
root->left = deleteNode(root->left,key);
}else
{
// 找到了要删除的节点
// 0) 下面这个包含了左右子树为空的情况
// 1) 要删除节点的左孩子为空,则使用右子树代替当前位置,返回右子树
if(root->left == nullptr)
{
return root->right;
}
// 2) 要删除节点的右孩子为空,则使用左子树代替当前位置,返回左子树
else if(root->right == nullptr)
{
return root->left;
}else
// 3) 要删除节点左右都不为空,找到右子树的最左侧,把左子树挂上
{
TreeNode *cur = root->right;
// 这里使用左子树进行判断,因为最后要使用cur 进行关联root的左子树
while(cur->left)
{
cur = cur->left;
}
cur->left = root->left;
return root->right;
}
}
return root;
}
};
复杂度分析
时间复杂度:O(N),一共有 N个节点,最坏情况下,每个节点都要遍历一次。
空间复杂度:O(N),递归深度最差情况下退化为N 。
思路分析
迭代
区别:在找到要删除的这个节点使用迭代的方式 ,找到之后删除的方法一致
程序代码
方法二:
class Solution {
public:
TreeNode* deleteOneNode(TreeNode *root)
{
if(root->left == nullptr)
{
return root->right;
}else if(root->right == nullptr)
{
return root->left;
}else
{
TreeNode *cur = root->right;
while(cur->left)
{
cur = cur->left;
}
cur->left = root->left;
return root->right;
}
}
TreeNode* deleteNode(TreeNode* root, int key) {
if(root == nullptr)
{
return nullptr;
}
// 开始找要删除的节点
TreeNode * cur = root;
// 记录其父节点,删除时要使用父节点进行删除
TreeNode *pre = nullptr;
while(cur != nullptr)
{
if(cur->val == key)
{
break;
}
if(cur->val > key)
{
pre = cur;
cur = cur->left;
}else
{
pre = cur;
cur = cur->right;
}
}
// 说明只有根节点
if(pre == nullptr)
{
return deleteOneNode(cur);
}
// 要删除的 key 在左孩子
if(pre->left != nullptr && pre->left->val == key){
pre->left = deleteOneNode(cur);
}
// 要删除的 key 在右孩子
if(pre->right != nullptr && pre->right->val == key){
pre->right = deleteOneNode(cur);
}
return root;
}
};
复杂度分析
时间复杂度:O(N),一共有 N个节点,最坏情况下,每个节点都要遍历一次。
空间复杂度:O(1) 。
更多相关知识:github.com/pingguo1987…