第22天 | ● 235. 二叉搜索树的最近公共祖先 ● 701.二叉搜索树中的插入操作 ● 450.删除二叉搜索树中的节点

第六章 二叉树part08

今日内容:

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

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

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

详细布置


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

相对于 二叉树的最近公共祖先 本题就简单一些了,因为 可以利用二叉搜索树的特性。

题目链接/文章讲解:代码随想录

视频讲解:https://www.bilibili.com/video/BV1Zt4y1F7ww

【链接】(文章,视频,题目)

【第一想法与实现(困难)】

  • 如何理由 二叉搜索树的有序性,也就是中序遍历(左中右)有序数组?其实本题连中序遍历都不需要,而是只需要利用二叉搜索树的定义:左子树都小于(等于),右子树都大于(等于),左右都是二叉搜索树

  • 又因为题设条件pq都存在,结点值不重复,就不会取上述等号

【看后想法】

  • 保证cur是公共祖先。不妨设置p < q,则只需要从上往下搜索,求第一个cur结点 使得cur 在 [p, q]的左闭右闭区间中。

  • 为什么第一个满足[p, q]的左闭右闭区间中的cur就是最近的公共祖先。如果再往下搜索,已知p <= cur <= q,说明pq分别属于cur的两个不同子树。left < cur < right(不重复),则向左不满足是右子树结点q的祖先,向右不满足是左子树结点p的祖先。

  • 小结:只需要找第一个cur in [p, q]左闭右闭即可,当自身是祖先也满足

【实现困难】

【自写代码】

class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        // 只需要找第一个cur in [p, q]左闭右闭即可,当自身是祖先也满足
        if (!root) {
            return nullptr;
        }
        if (root->val < p->val && root->val < q->val) {
            // p,q在右边
            return lowestCommonAncestor(root->right, p, q);
        }
        if (root->val > p->val && root->val > q->val) {
            // p,q在左边
            return lowestCommonAncestor(root->left, p, q);
        }
        // root正是[p, q]左闭右闭之中
        return root;
    }
};
class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        // 迭代方法
        if (!root) {
            return nullptr;
        }
        TreeNode* cur = root;
        while (cur) {
            if (cur->val < p->val && cur->val < q->val) {
                // p,q在右边
                cur = cur->right;
            } else if (cur->val > p->val && cur->val > q->val) {
                // p,q在左边
                cur = cur->left;
            } else {
                // 找到了所求的cur in [p, q]左闭右闭,min(p,q) <= cur <= max(p,q)
                return cur;
            }
        }
        // 循环结束没找到
        return cur;
    }
};

【收获与时长】40m


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

本题比想象中的简单,大家可以先自己想一想应该怎么做,然后看视频讲解,就发现 本题为什么比较简单了。

题目链接/文章讲解:代码随想录

视频讲解:https://www.bilibili.com/video/BV1Et4y1c78Y

【链接】(文章,视频,题目)

【第一想法与实现(困难)】

  • 题目描述,可以不必重构搜索二叉搜索树

  • 直接插入成叶子结点。记录插入位置及其双亲结点

class Solution {
public:
    TreeNode* insertIntoBST(TreeNode* root, int val) {
        // 直接插入成叶子结点。记录插入位置及其双亲结点
        if (!root) {
            return new TreeNode(val);
        }
        TreeNode* cur = root;
        TreeNode* pre = nullptr;
        while (cur) {
            if (val < cur->val) {
                pre = cur;
                cur = cur->left;
            } else {
                pre = cur;
                cur = cur->right;
            }
        }
        // 构造pre指向cur的指向关系,cur为待插入结点位置,pre为双亲结点
        cur = new TreeNode(val);
        if (val < pre->val) {
            pre->left = cur;
        } else {
            pre->right = cur;
        }
        return root;
    }
};

【看后想法】

  • 要注意处理空树的特殊情况,直接返回一个新节点

【实现困难】

【自写代码】

  • 递归方法,直接更新左右结点
class Solution {
public:
    TreeNode* insertIntoBST(TreeNode* root, int val) {
        // 递归方法,直接更新左右结点
        // 1递归返回值和参数:复用
        // 2递归结束条件:空节点
        // 3递归单层逻辑:判断左插入还是右插入,直接更新左或右结点(要么返回结点自身,要么从空节点更新成新插入的结点)
        if (!root) {
            return new TreeNode(val);
        }
        if (val < root->val) {
            root->left = insertIntoBST(root->left, val);
        } else {
            root->right = insertIntoBST(root->right, val);
        }
        return root;
    }
};
  • 通过递归函数的返回值完成父子节点的赋值是可以带来便利的

  • 递归方法,使用空返回值,需要记录双亲结点

class Solution {
// 递归方法,使用空返回值,需要记录双亲结点
private:
    TreeNode* parent = nullptr;
    // 1递归参数返回值,空返回值
    void Traverse(TreeNode* root, int val) {
        // 2递归结束条件,空节点
        if (!root) {
            TreeNode* node = new TreeNode(val);
            if (val < parent->val) {
                parent->left = node;
            } else {
                parent->right = node;
            }
            return;
        }
        // 3递归单层逻辑,判断插入到左还是右子树
        parent = root;
        if (val < root->val) {
            Traverse(root->left, val);
        } else {
            Traverse(root->right, val);
        }
    }
public:
    TreeNode* insertIntoBST(TreeNode* root, int val) {
        if (!root) {
            // 空树
            return new TreeNode(val);
        }
        Traverse(root, val);
        return root;
    }
};

【收获与时长】30m


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

相对于 插入操作,本题就有难度了,涉及到改树的结构

题目链接/文章讲解:代码随想录

视频讲解:https://www.bilibili.com/video/BV1tP41177us

【链接】(文章,视频,题目)

【第一想法与实现(困难)】想不出来,直接看题解

【看后想法】

  • 首先找到待删除结点,然后删除。利用二叉搜索树的性质,二叉搜索地查找,容易想到递归

  • 递归三步

    • 1返回值参数,节点指针作为返回值给上层调用接住

    • 2递归终止条件,分类讨论:

      • 1没找到,返回空

      • 2(以下都是找到了)没有左右孩子,删除自己返回空

      • 3只有左孩子,删除自己返回左孩子

      • 4只有右孩子,删除自己返回右孩子

      • 5有左右孩子,保留其中右子树,利用二叉搜索树性质,将左子树放到适合的位置,也就是右子树的最小位置(递归左孩子,最左孩子)的左孩子。删除自己返回保留的右孩子。(注意这里保留右子树也可以的,需要一点修改)

    • 3单层递归逻辑,二叉搜索,目标值小则递归删除左孩子,目标值大则递归删除右孩子

【实现困难】

【自写代码】

递归方法

class Solution {
public:
    // 首先找到待删除结点,然后删除。利用二叉搜索树的性质,二叉搜索地查找,容易想到递归
    TreeNode* deleteNode(TreeNode* root, int key) {
        // 递归三部曲
        // 1返回值参数,节点指针作为返回值给上层调用接住
        // 2递归终止条件,分类讨论
        // 2.1没找到,返回空
        if (!root) {
            return nullptr;
        }
        // (以下都是找到了)
        if (key == root->val) {
            // 2.2没有左右孩子,删除自己返回空
            if (!root->left && !root->right) {
                delete root;
                return nullptr;
            }
            // 2.3只有左孩子,删除自己返回左孩子
            if (root->left && !root->right) {
                TreeNode* res = root->left;
                delete root;
                return res;
            }
            // 2.4只有右孩子,删除自己返回右孩子
            if (!root->left && root->right) {
                TreeNode* res = root->right;
                delete root;
                return res;
            }
            // 2.5有左右孩子,保留其中右子树,利用二叉搜索树性质,将左子树放到适合的位置,也就是右子树的最小位置(递归左孩子,最左孩子)的左孩子。删除自己返回保留的右孩子。(注意这里保留右子树也可以的,需要一点修改)
            TreeNode* cur = root->right;
            while (cur->left) {
                cur = cur->left;
            }
            cur->left = root->left;
            TreeNode* res = root->right;
            delete root;
            return res;
        }
        // 3单层递归逻辑,二叉搜索,目标值小则递归删除左孩子,目标值大则递归删除右孩子
        if (key < root->val) {
            root->left = deleteNode(root->left, key);
        }
        if (key > root->val) {
            root->right = deleteNode(root->right, key);
        }
        return root;
    }
};

迭代方法

class Solution {
// 迭代方法,需要记录待删除结点的双亲
private:
    TreeNode* DeleteOneNode(TreeNode* cur) {
        if (!cur) {
            return nullptr;
        }
        if (!cur->left && !cur->right) {
            delete cur;
            return nullptr;
        }
        if (cur->left && !cur->right) {
            TreeNode* res = cur->left;
            delete cur;
            return res;
        }
        if (!cur->left && cur->right) {
            TreeNode* res = cur->right;
            delete cur;
            return res;
        }
        TreeNode* left_most_of_right = cur->right;
        while (left_most_of_right->left) {
            left_most_of_right = left_most_of_right->left;
        }
        left_most_of_right->left = cur->left;
        TreeNode* res = cur->right;
        delete cur;
        return res;
    }
public:
    TreeNode* deleteNode(TreeNode* root, int key) {
        // 保证至少有一个根节点非空
        if (!root) {
            return nullptr;
        }
        TreeNode* cur = root;
        TreeNode* parent = nullptr;
        while (cur) {
            if (key == cur->val) {
                break;
            }
            parent = cur;
            if (key < cur->val) {
                cur = cur->left;
            } else {
                cur = cur->right;
            }
        }
        // 只有根节点,并且就是待删除结点
        if (!parent) {
            return DeleteOneNode(root);
        }
        // 找到了,分类讨论
        if (parent->left && parent->left == cur) {
            parent->left = DeleteOneNode(cur);
        }
        if (parent->right && parent->right == cur) {
            parent->right = DeleteOneNode(cur);
        }
        return root;
    }
};

【收获与时长】1h

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值