LeetCode 450.删除二叉搜索树中的节点

LeetCode 450.删除二叉搜索树中的节点

1、题目

题目链接:450. 删除二叉搜索树中的节点
给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。
一般来说,删除节点可分为两个步骤:

  1. 首先找到需要删除的节点;
  2. 如果找到了,删除它。

示例 1:
image.png

输入: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 为树的高度。

2、递归法(二叉搜索树)

思路

二叉搜索树有以下性质:

  • 左子树的所有节点(如果有)的值均小于当前节点的值;
  • 右子树的所有节点(如果有)的值均大于当前节点的值;
  • 左子树和右子树均为二叉搜索树。

二叉搜索树的题目往往可以用递归来解决。此题要求删除二叉搜索树的节点,函数 deleteNode 的参数是二叉搜索树的根节点 root 和一个整数 key,返回值是删除值为 key 的节点后的二叉搜索树的根节点,并保持二叉搜索树的有序性。可以按照以下情况分类讨论:

  1. 没找到删除的节点,遍历到空节点直接返回了
  2. 找到删除的节点
    1. 第二种情况:左右孩子都为空(叶子节点),不用改变二叉树的结构),直接删除节点,返回 nullptr 为根节点
    2. 第三种情况:删除节点的左孩子为空,右孩子不为空,删除节点,右孩子补位,返回右孩子为根节点
    3. 第四种情况:删除节点的右孩子为空,左孩子不为空,删除节点,左孩子补位,返回左孩子为根节点
    4. 第五种情况:左右孩子节点都不为空,则将删除节点的左子树头结点(左孩子)放到删除节点的右子树的最左面节点的左孩子上,返回删除节点右孩子为新的根节点。

第五种情况有点难以理解,看下面动画:

动画中的二叉搜索树中,删除元素7, 那么删除节点(元素7)的左孩子就是5,删除节点(元素7)的右子树的最左面节点是元素8。
将删除节点(元素7)的左孩子放到删除节点(元素7)的右子树的最左面节点(元素8)的左孩子上,就是把5为根节点的子树移到了8的左孩子的位置。
要删除的节点(元素7)的右孩子(元素9)为新的根节点。.
这样就完成删除元素7的逻辑,最好动手画一个图,尝试删除一个节点试试。

代码

class Solution {
public:
    TreeNode* deleteNode(TreeNode* root, int key) {
        if (root == nullptr) { // 第一种情况:没找到删除的节点,遍历到空节点直接返回了
            return nullptr;
        }
        if (root->val == key) { // 如果当前节点的值等于要删除的键
            if (root->left == nullptr && root->right == nullptr) { // 第二种情况:左右孩子都为空(叶子节点),直接删除节点, 返回nullptr为根节点
                delete root;
                return nullptr;
            } else if (root->left == nullptr) { // 第三种情况:删除节点的左孩子为空,右孩子不为空,删除节点,右孩子补位,返回右孩子为根节点
                TreeNode* node = root->right;
                delete root;
                return node;
            } else if (root->right == nullptr) { // 第四种情况:删除节点的右孩子为空,左孩子不为空,删除节点,左孩子补位,返回左孩子为根节点
                TreeNode* node = root->left;
                delete root;
                return node;
            } else { // 第五种情况:左右孩子节点都不为空,则将删除节点的左子树头结点(左孩子)放到删除节点的右子树的最左面节点的左孩子上,返回删除节点右孩子为新的根节点。
                TreeNode* cur = root->right;
                // 找到右子树中最小的节点
                while (cur->left != nullptr) {
                    cur = cur->left;
                }
                // 将当前节点(要删除的节点(root))的左子树接到右子树中最小的节点的左子树上
                cur->left = root->left;
                // 把root节点保存一下,下面来删除
                TreeNode* tmp = root;
                // 将当前节点的右子树赋值给根节点
                root = root->right;
                // 删除当前节点
                delete tmp;
                return root;
            }
        }
        if (root->val > key) {
            // 如果当前节点的值大于要删除的key,在左子树中递归删除节点
            root->left = deleteNode(root->left, key);
        } else {
            // 如果当前节点的值小于要删除的key,在右子树中递归删除节点
            root->right = deleteNode(root->right, key);
        }
        return root;
    }
};

复杂度分析

  • 时间复杂度: O(n)
  • 空间复杂度: O(n)

3、递归法(普通二叉树)

思路

普通二叉树的删除方式(没有使用搜索树的特性,遍历整棵树),用交换值的操作来删除目标节点。
代码中目标节点(要删除的节点)被操作了两次:

  • 第一次是和目标节点的右子树最左面节点交换。
  • 第二次直接被nullptr覆盖了。

代码

class Solution {
public:
    TreeNode* deleteNode(TreeNode* root, int key) {
        if (root == nullptr) {
            return root;
        }
        if (root->val == key) {
            if (root->right == nullptr) {
                // 这里第二次操作目标值:最终删除的作用
                return root->left;
            }
            TreeNode *cur = root->right;
            while (cur->left) {
                cur = cur->left;
            }
            // 这里第一次操作目标值:交换当前节点的值与其右子树最左面节点的值
            swap(root->val, cur->val);
        }
        // 递归删除左子树中值为key的节点
        root->left = deleteNode(root->left, key);
        // 递归删除右子树中值为key的节点
        root->right = deleteNode(root->right, key);
        return root;
    }
};

复杂度分析

  • 时间复杂度: O(n)
  • 空间复杂度: O(n)

4、迭代法

思路

  1. deleteOneNode 函数:将目标节点的左子树移到其右子树的最左面节点的左孩子位置,并返回新的根节点(即目标节点的右孩子)。
    • 如果目标节点没有右子树,直接返回其左子树作为新的根节点。
    • 如果目标节点有右子树,找到右子树的最左面节点,并将目标节点的左子树作为该最左面节点的左孩子。最后返回目标节点的右子树作为新的根节点。
  2. deleteNode函数:从二叉搜索树中删除值为 key 的节点,并返回新的根节点。
    • 首先检查根节点是否为空,如果为空,则直接返回。
    • 使用 cur 和 pre 两个指针来遍历树,找到值为 key 的节点。其中 cur 用于遍历,pre 用于记录 cur 的父节点,以便后续删除操作。
    • 如果在遍历过程中找到了值为 key 的节点,则跳出循环。
    • 如果 pre 为空,说明要删除的节点是根节点,直接调用 deleteOneNode 函数处理并返回新的根节点。
    • 如果 pre 不为空,需要判断 cur 是 pre 的左孩子还是右孩子。然后调用 deleteOneNode 函数处理 cur,并将处理后的结果赋值给 pre 的对应孩子,完成删除操作。
    • 最后返回根节点 root。

代码

class Solution {
private:
    // 将目标节点(删除节点)的左子树放到目标节点的右子树的最左面节点的左孩子位置上,并返回目标节点右孩子为新的根节点
    TreeNode* deleteOneNode(TreeNode* target) {
        if (target == nullptr) return target;
        if (target->right == nullptr) return target->left;
        TreeNode* cur = target->right;
        while (cur->left) {
            cur = cur->left;
        }
        cur->left = target->left;
        return target->right;
    }
public:
    TreeNode* deleteNode(TreeNode* root, int key) {
        if (root == nullptr) return root;
        TreeNode* cur = root;
        TreeNode* pre = nullptr; // 记录cur的父节点,用来删除cur
        while (cur) {
            if (cur->val == key) break;
            pre = cur;
            if (cur->val > key) cur = cur->left;
            else cur = cur->right;
        }
        if (pre == nullptr) { // 如果搜索树只有头结点
            return deleteOneNode(cur);
        }
        // pre 要知道是删左孩子还是右孩子
        if (pre->left && pre->left->val == key) {
            pre->left = deleteOneNode(cur);
        }
        if (pre->right && pre->right->val == key) {
            pre->right = deleteOneNode(cur);
        }
        return root;
    }
};

复杂度分析

  • 时间复杂度: O(n)
  • 空间复杂度: O(1)
  • 28
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值