【代码随想录算法训练Day22】LeetCode 235.二叉搜索树的最近公共祖先、LeetCode 701.二叉搜索树中的插入操作、LeetCode 450.删除二叉搜索树中的节点

235. 二叉搜索树的最近公共祖先

本题使用 二叉树的最近公共祖先 这题的代码同样能解,但本题的给出了额外的可利用的信息,这棵树是二叉搜索树,利用这个信息可以减少搜索的复杂度。

本题是二叉搜索树,二叉搜索树是有序的,在有序树里,如果判断一个节点的左子树里有p,右子树里有q, 那么这个节点就是p,q的公共祖先。

换句话说,如果 中间节点是 q 和 p 的公共祖先,那么 中节点的数组 一定是在 [p, q]区间的。即 中节点 > p && 中节点 < q 或者 中节点 > q && 中节点 < p。

那么只要从上到下去遍历,遇到 cur节点是数值在[p, q]区间中则一定可以说明该节点cur就是p 和 q的公共祖先。并且,如果能确认到 p和q 分别是在左子树和右子树中,那么该节点必然是最近的公共祖先,因为,从该节点开始,再往左子树遍历,那么将确认不到 q,因为 q 在该节点的右子树上,p 也同理。所以只要判断到一个节点的左子树里有p,右子树里有q,那么该节点就是最近公共祖先。

再利用上二叉搜索树的特性,如果 p, q 的 val 都比当前节点 val 大的话,那么 p, q 只会出现在当前节点的右子树上; 都比当前节点 val 小时,则在左子树上。

通过这些特性,能够减少搜索时需要遍历的节点数。701.二叉搜索树中的插入操作

class Solution {
public:
    TreeNode* traversal(TreeNode* cur, TreeNode* p, TreeNode* q){
        if(cur == NULL) return NULL;
        TreeNode* left = NULL;
        TreeNode* right = NULL;
        if(cur->val > p->val && cur->val > q->val){
            left = traversal(cur->left, p, q);
            if(left) return left;
        }
        if(cur->val < p->val && cur->val < q->val){
            right = traversal(cur->right, p, q);
            if(right) return right;
        }
        return cur;
    }

    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        return traversal(root, p, q);
    }
};

701.二叉搜索树中的插入操作

二叉搜索树中插入节点的插入方法有很多,可以插到中间,开头,之后再维护整棵树,使其满足二叉搜索树的性质。我最开始就是这么想的,像维护堆一样,维护这棵树。

但其实这么做就复杂了。

这题的技巧在于,在二叉搜索树中插入节点的操作,插入的节点可以直接当作都是叶子节点,只要遍历二叉搜索树,找到满足二叉搜索树性质的空节点 插入元素就可以了,这样插入节点并不会影响整棵树的性质,还省了维护的过程。

class Solution {
public:
    TreeNode* insertIntoBST(TreeNode* root, int val) {
        if(!root){
            TreeNode* node = new TreeNode(val);
            return node;
        }
        if(root->val > val) root->left = insertIntoBST(root->left, val);
        if(root->val < val) root->right = insertIntoBST(root->right, val);
        return root;
    }
};

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

这题的思路是,将将要删除节点的左子树插入到其右子树的最左侧节点,这样就能保证不破坏二叉搜索树的性质。

class Solution {
public:
    TreeNode* insertIntoLeftmost(TreeNode* cur, TreeNode* node){
        if(!cur){
            return node;
        }
        TreeNode* last = cur;
        while (last->left) {
            last = last->left;
        }
        last->left = node; // 将 node 插入到最左侧叶子节点的左侧
        return cur;
    }

    TreeNode* deleteNode(TreeNode* root, int key) {
        if(root == nullptr) return root;
        if(root->val == key){
            if (root->left == nullptr) {
                TreeNode* rightChild = root->right;
                delete root;
                return rightChild;
            }
            if (root->right == nullptr) {
                TreeNode* leftChild = root->left;
                delete root;
                return leftChild;
            }
            TreeNode* node = root->right; // 获取右子树
            node = insertIntoLeftmost(node, root->left); // 将左子树插入右子树的最左侧
            delete root;
            return node;
        }
        if(root->val > key) root->left = deleteNode(root->left, key);
        if(root->val < key) root->right = deleteNode(root->right, key);
        return root;
    }
};

但其实,更常见的删除思路是用其右子树中的最小节点(或左子树中的最大节点)来替换要删除的节点,然后删除那个最小(或最大)节点。

这里我分成了两个函数来实现,一个负责查找,一个负责删除,逻辑部分分成两块,各函数独立完成自己的任务。

这些么写之后需要在delete函数中,调用完getminimum后再调用delete来删除替换过来的节点。如下所示。

TreeNode* minNode = getMinimum(root->right);
root->val = minNode->val;
root->right = deleteNode(root->right, minNode->val);

 在执行 deleteNode(root->right, minNode->val) 时,你实际上是:

  • 首先确定了最小节点的位置。
  • 然后替换值,并且递归地调用 deleteNode 来精确删除那个位置的节点。因为这个最小节点没有左子节点,这个删除操作变得非常直接和简单。

完整代码如下所示

TreeNode* getMinimum(TreeNode* node) {
    while (node->left != nullptr) {
        node = node->left;
    }
    return node;
}

TreeNode* deleteNode(TreeNode* root, int key) {
    if (!root) return nullptr;

    if (key < root->val) {
        root->left = deleteNode(root->left, key);
    } else if (key > root->val) {
        root->right = deleteNode(root->right, key);
    } else {
        if (!root->left) {
            TreeNode* rightChild = root->right;
            delete root;
            return rightChild;
        }
        if (!root->right) {
            TreeNode* leftChild = root->left;
            delete root;
            return leftChild;
        }

        // 当存在两个子节点时,找到右子树中最小的节点并替换当前节点的值,然后删除那个最小的节点
        TreeNode* minNode = getMinimum(root->right);
        root->val = minNode->val;
        root->right = deleteNode(root->right, minNode->val);
    }

    return root;
}

  • 9
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值