代码随想录DAY20 - 二叉树 - 08/19

目录

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

题干

思路和代码

递归法

迭代法

二叉搜索树的插入操作

题干

思路和代码

递归法

迭代法

删除二叉搜索树的结点

题干

思路

代码

方法一:递归法

方法二:拆解删除步骤


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

题干

题目:给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。

说明:

  • 所有节点的值都是唯一的。

  • p、q 为不同节点且均存在于给定的二叉搜索树中。

链接:. - 力扣(LeetCode)

思路和代码

之前做了 “二叉树的最近公共祖先” 问题,是用后序遍历层层向上返回子树是否有 p、q 结点。但这道题是要找 二叉搜索树 的最近公共祖先,可以利用二叉树结点有序的特性,来寻找 p、q。同时也不需要回溯,因为二叉搜索树自带方向性,只要自顶向下遍历找到 p、q 所在的区间即可。

递归法
  • 递归参数和返回值:参数是传入的当前结点,要查询的 p、q 结点;无返回值。在遍历过程中不断更新公共祖先 ancestor。

  • 递归结束的条件:当找到公共祖先时,立即返回,不需要再往下遍历了。

  • 递归顺序:先比较 p、q 和当前结点 root 的大小,再根据大小情况遍历左右子树。

    • 如果 p、q 都比当前结点大,则遍历右子树。

    • 如果 p、q 都比当前结点小,则遍历左子树。

    • 其余情况,当前结点即为公共祖先:

      • 如果 p < 当前结点 < q 或 q < 当前结点 < p,则说明当前结点就是最近公共祖先,直接返回。

      • 如果 p = 当前结点 或 q = 当前结点,由于二叉搜索树有序,则另一个结点肯定在子树中,当前结点即为最近公共祖先,直接返回。

class Solution {
public:
    TreeNode* ancestor = nullptr; // 记录公共祖先结点
    void findAncestor(TreeNode* root, TreeNode* p, TreeNode* q){
        if (root == nullptr) return;
        int minValue = min(p->val,q->val);
        int maxValue = max(p->val,q->val);

        if (maxValue < root->val){ // 遍历左子树
             findAncestor(root->left,p,q);
            if (ancestor != nullptr) return;
        } else if (minValue > root->val){ // 遍历右子树
             findAncestor(root->right,p,q);
            if (ancestor != nullptr) return;
        } else { // 找到公共祖先
            ancestor = root;
            return;
        }
    }
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        findAncestor(root,p,q);
        return ancestor;
    }
};
迭代法

思路和递归法相同,只不过将递归转为迭代而已。

class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        TreeNode* ancestor = nullptr; // 记录公共祖先
        TreeNode* cur = root; // 遍历二叉树
        while (ancestor == nullptr){
            if (p->val > cur->val && q->val > cur->val){
                // 如果 p、q 都比当前结点要大,遍历右子树
                cur = cur->right;
            } else if (p->val < cur->val && q->val < cur->val){
                 // 如果 p、q 都比当前结点要小,遍历左子树
                cur = cur->left;
            } else{
                ancestor = cur;
            }
        }
        return ancestor;
    }
};

二叉搜索树的插入操作

题干

题目:给定二叉搜索树(BST)的根节点 root 和要插入树中的值 value ,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。

说明:输入数据 保证 ,新值和原始二叉搜索树中的任意节点值都不同。

注意,可能存在多种有效的插入方式,只要树在插入后仍保持为二叉搜索树即可。 你可以返回 任意有效的结果 。

链接:. - 力扣(LeetCode)

思路和代码

比较容易的方法是找到一个插入位置刚好是末尾结点处,这样就可以直接插入,并不需要修改树的结构。

基本思路就是从上往下遍历,比较 val 和当前结点的大小,若大于则往右子树遍历,小于则往左子树遍历,直到找到末尾节点插入。

递归法
class Solution {
public:
    TreeNode* insertIntoBST(TreeNode* root, int val) {
        // 找到插入位置为末尾结点时,返回要新插入的结点
        if (root == nullptr){
            TreeNode* newNode = new TreeNode(val);
            return newNode;
        }
        if (val > root->val){
            TreeNode* newNode = insertIntoBST(root->right,val);
            // 若新节点不为空,说明已经插入,将新插入的结点返回给上一层父节点
            if (newNode){
                root->right = newNode;
            }
        } else if (val < root->val){
            TreeNode* newNode = insertIntoBST(root->left,val);
            if (newNode){
                root->left = newNode;
            }
        }
        return root;
    }
};
迭代法
class Solution {
public:
    TreeNode* insertIntoBST(TreeNode* root, int val) {
        // 若初始为空结点,直接返回新建结点
        if(root == nullptr) return new TreeNode(val);
        TreeNode* cur = root;
        // 从根节点开始往后遍历
        while (cur){
            if (val < root->val){
                if (cur->left == nullptr){
                    cur->left = new TreeNode(val);
                    break;
                } else{
                    cur = cur->left;
                }
            } else if (val > root->val){
                if (cur->right == nullptr){
                    cur->right = new TreeNode(val);
                    break;
                } else{
                    cur = cur->right;
                }
            }
        }
        return root;
    }
};

删除二叉搜索树的结点

题干

题目:给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。

一般来说,删除节点可分为两个步骤:

  • 首先找到需要删除的节点;

  • 如果找到了,删除它。

说明:二叉树的节点值唯一。

链接:. - 力扣(LeetCode)

思路

首先要找到待删除的结点,若没有找到,说明结点不在树中,直接返回原根节点 root;若找到待删除的结点,需要调整二叉树。

如何调整?只需要让待删除结点的左右子树合并为一颗二叉搜索树,再将合并后的二叉搜索树的根节点替代掉删除的位置。

最关键的问题就是如何合并两棵子树?

  • 待删除结点是叶子结点,没有左右子树,则合并后的子树为空。

  • 待删除结点只有一棵子树,直接让子树的根节点替代掉删除结点。

  • 待删除结点有两棵子树,此时有两种方法。

    • 方法一:将左子树合并到右子树中,找到右子树的最左边结点(即右子树的最小值),让左子树插入到此结点的左孩子处。

    • 方法二:从左右子树中选择一个结点作为新的根结点,为了保持二叉搜索树的特性,这个根节点可以是左子树的最右结点(即左子树的最大值)或右子树的最左结点(即右子树的最小值)。

代码

方法一:递归法

将问题拆解为在左右子树中删除结点,并返回删除后子树的新结点给上层。

  • 递归参数和返回值:参数是传入的根节点和要删除的节点值。返回值是删除结点后经过调整的子树的根。

  • 递归结束的条件:当结点为空,说明没找到,返回空结点;当找到删除结点,执行删除操作,返回。

  • 递归顺序:采用前序遍历,先比较当前结点是否为被删除结点,如果当前结点 < 删除结点,遍历右子树;如果当前结点 > 删除结点,遍历左子树。

class Solution {
public:
    TreeNode* pre = nullptr; // 记录上一个结点
    TreeNode* deleteNode(TreeNode* root, int key) {
        if (root == nullptr) return nullptr; // 遍历到最后,没有找到被删除的结点
        if (root->val == key){
            // 找到了被删除的结点
            // 1)叶子节点
            if (root->left == nullptr && root->right == nullptr){
                return nullptr; // 返回空结点
            } else if (root->left != nullptr && root->right == nullptr){
            // 2)左子树非空,右子树空
                return root->left;
            } else if (root->left == nullptr && root->right != nullptr){
            // 3)左子树空,右子树非空
                return root->right;
            } else{
            // 4)左右子树非空,需要返回新结点,先找到右子树的最左边结点,让最左边结点的左孩子为删除节点的左子树
                TreeNode* cur = root->right;
                while (cur->left != nullptr){
                    cur = cur->left;
                }
                cur->left = root->left;
                return root->right;
            }
        }
        pre = root; // 记录遍历过的上一层结点
        if (root->val < key) root->right = deleteNode(root->right,key); // 遍历右子树
        if (root->val > key) root->left = deleteNode(root->left,key); // 遍历左子树
        return root;
    }
};
方法二:拆解删除步骤

删除的步骤有三步:

  • 找到待删除的结点,定义为一个方法 findKey()

  • 合并待删除结点的左右子树,定义为一个方法 mergeTwoTree()

  • 将合并后的树的根节点替换掉待删除的结点,在方法 deleteNode() 中执行。

三个步骤方法合起来的时间复杂度是 O(h)。

class Solution {
public:
    TreeNode* pre = nullptr; // 记录待删除结点的父结点
    // 查找待删除结点
    TreeNode* findKey(TreeNode* root, int key){
        if (root == nullptr) return root;
        TreeNode* keyNode = nullptr;
        if (key < root->val){
            pre = root;
            // pre 一定要在 keyNode之前
            keyNode = findKey(root->left,key);
        } else if (key > root->val){
            pre = root;
            keyNode = findKey(root->right,key);
        } else{
            keyNode = root;
        }
        return keyNode;
    }
    // 合并左右子树
    TreeNode* mergeTwoTree(TreeNode* leftTree, TreeNode* rightTree){
        if (leftTree == nullptr && rightTree == nullptr){ // 1)左右子树为空,合并后的子树也为空结点
            return nullptr;
        } else if (leftTree == nullptr && rightTree != nullptr){ // 2)左子树空,右子树非空,合并后的子树即右子树
            return rightTree;
        } else if (leftTree != nullptr && rightTree == nullptr){ // 3)左子树空,右子树非空,合并后的子树即右子树  
            return leftTree;
        } else{ // 4)左右子树非空
            TreeNode* cur = leftTree; // 找到左子树的最右结点,作为左右子树的根节点
            TreeNode* father = nullptr; // 找到 cur 的父节点
            while (cur->right != nullptr){
                father = cur;
                cur = cur->right;
            }
            if (father == nullptr){
                cur->right = rightTree;
            } else{
                father->right = cur->left;
                cur->left = leftTree;
                cur->right = rightTree;
            }
            return cur;
        }
    }
    // 删除结点
    TreeNode* deleteNode(TreeNode* root, int key) {
        if (root == nullptr) return root;
        // 找到待删除的结点
        TreeNode* keyNode = findKey(root,key);
        if (keyNode == nullptr) return root;
        // 将删除节点的左右子树进行合并
        TreeNode* newNode = mergeTwoTree(keyNode->left,keyNode->right);
        if (pre == nullptr){
            // 如果 pre 为空,说明要删除的结点是根结点
            root = newNode;
        } else{
            if (pre->right == keyNode){
                pre->right = newNode;
            } else{
                pre->left = newNode;
            }
        }
        return root;
    }
};
  • 9
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值