代码随想录算法训练营第20天 | LeetCode235.二叉搜索树的最近公共祖先、LeetCode701.二叉搜索树中的插入操作、LeetCode450.删除二叉搜索树中的结点

目录

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

1. 递归法

1.1 普通二叉树

1.2 二叉搜索树

2. 迭代法

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

1. 递归法

1.1 无返回值

1.2 有返回值

2. 迭代法(双指针法)

LeetCode450.删除二叉搜索树中的结点

1. 递归法 

1.1 普通二叉树

1.2 二叉搜索树

2. 迭代法


 

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

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

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

思路:之前已经讲过二叉树如何求给定两个结点的最近公共祖先, 对于二叉搜索树来说是通用的,但是二叉搜索树本身是有序的,我们可以根据有序这一特性来进行求解。

这里大体分为递归法迭代法

1. 递归法

1.1 普通二叉树

对于普通二叉树是将整棵树都遍历一次,包括左右子树,然后根据左右子树的情况进行逻辑处理,之前讲过,这里不再赘述。

TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if(root == p || root == q || root == NULL) return root;
        TreeNode* left = lowestCommonAncestor(root -> left, p, q);
        TreeNode* right = lowestCommonAncestor(root -> right, p, q);
        if(left != NULL && right != NULL) return root;
        if(left == NULL) return right;
        return left;
    }

1.2 二叉搜索树

对于二叉搜索树,需要知道,当所遍历的结点的值处于要找的两个结点之间时,该结点必为最近的公共结点。所以我们需要在此时返回即可。

这里不同于一般的二叉树找最近公共结点需要遍历整棵树,这里只要找到了就立即返回,不需要遍历整棵树。

    TreeNode* traversal(TreeNode* cur, TreeNode* p, TreeNode* q){
        if(cur == NULL) return NULL;   //中

        if(cur -> val > p -> val && cur ->val > q -> val){//左
            //当当前结点的值大于p和q的值时,说明应该向左子树查找
            //注意此时并不知道p和q谁大,所以都要比较
            TreeNode* left = traversal(cur -> left, p, q);
            if(left) return left;//当left不为空,直接返回
        } 
        if(cur -> val < p -> val && cur -> val < q -> val){//右
            //同样的道理,当当前结点的值小于p和q的值时,说明应该向右子树查找
            TreeNode* right = traversal(cur -> right, p, q);
            if(right) return right;//当right不为空,直接返回
        }
        return cur;//其余情况的话直接返回当前结点即为所找最近公共祖先
        
    }
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        return traversal(root, p, q);
    }

 下面的代码是对上面代码的精化。

    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if(root -> val > p -> val && root -> val > q -> val){
            return lowestCommonAncestor(root -> left, p, q);
        }else if(root -> val < p -> val && root -> val < q -> val){
            return lowestCommonAncestor(root -> right, p, q);
        }else{
            return root;
        }
    }

2. 迭代法

采用迭代法的思路起始和递归的二叉搜索树的思路一样,只不过实现方式是while循环,不是递归进行的处理。

    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        while(root != NULL){
            if(root -> val > p -> val && root -> val > q -> val){
                root = root -> left;
            }else if(root -> val < p -> val && root -> val < q -> val){
                root = root -> right;
            }else return root;
        }
        return NULL;
    }

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

给定二叉搜索树(BST)的根节点 root 和要插入树中的值 value ,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 输入数据 保证 ,新值和原始二叉搜索树中的任意节点值都不同。

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

思路:虽然题目中说可以重构,也就是存在多种结果,只要有效。但是其实没有必要进行重构,只要根据二叉树的有序的特点进行分向,在遇到空结点的时候进行添加就行。这里同样分为递归法迭代法

1. 递归法

1.1 无返回值

如果没有返回值,那么注意需要将树进行&引用,否则没有办法将新增结点加入树中。

    void insertNode(TreeNode*& cur, int val){//对cur需要引用,否则修改不成功
        if(cur == NULL){
            TreeNode* node = new TreeNode(val);
            cur = node;//创建一个新的结点,使得cur指向新的结点
            return;
        }
        if(cur -> val > val) insertNode(cur -> left, val);
        if(cur -> val < val) insertNode(cur -> right, val);
        return;
    }
    TreeNode* insertIntoBST(TreeNode* root, int val) {
        insertNode(root, val);
        return root;
    }

 当然也可以使用parent指针指向前一个结点,这样就能够判断新增结点是前一个结点的左孩子还有右孩子。

    TreeNode* parent = NULL;//将加入结点的父节点记录下,完成最后的赋值
    void insertNode(TreeNode* cur, int val){
        if(cur == NULL){
            TreeNode* node = new TreeNode(val);
            if(parent -> val > val) parent -> left = node;
            else parent -> right = node;
            return;
        }
        parent = cur;//指向前一个结点,成为加入结点的父节点
        if(cur -> val > val) insertNode(cur -> left, val);
        if(cur -> val < val) insertNode(cur -> right, val);
        return;
    }
    TreeNode* insertIntoBST(TreeNode* root, int val) {
        if(root == NULL){
            root = new TreeNode(val);
            return root;
        }
        insertNode(root, val);
        return root;
    }

1.2 有返回值

相较于递归没有返回值,有返回值的代码更加简洁,每次递归返回的时候,使用结点的左孩子或右孩子指针接住,即可完成父子结点的融合。

    TreeNode* insertIntoBST(TreeNode* root, int val) {
        if(root == NULL){
            root = new TreeNode(val);
            return root;
        }
        if(root -> val > val) root -> left = insertIntoBST(root -> left, val);
        if(root -> val < val) root -> right = insertIntoBST(root -> right, val);
        return root;
    }

2. 迭代法(双指针法)

迭代法中采用双指针方法,使用parent指针指向新增结点的前一个结点,方便后续添加处理。

    TreeNode* insertIntoBST(TreeNode* root, int val) {
        TreeNode* node =  new TreeNode(val);
        if(root == NULL){
            root = node;
            return root;
        }
        TreeNode* cur = root;
        TreeNode* parent = NULL;//记录父节点位置
        while(cur != NULL){
            parent = cur;
            if(cur -> val > val){
                cur = cur -> left;
            }else {
                cur = cur -> right;
            }
        }
        if(parent ->val > val) parent -> left = node;
        else parent -> right = node;
        return root;
    }

LeetCode450.删除二叉搜索树中的结点

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

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

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

思路:删除结点相较于增加结点来说存在一定的难度,因为存在多种处理情况。

这里将其概括为5种情况。

1、树中不存在删除结点

2、树中存在删除结点,左右子树都为空(即根节点)

3、树中存在删除结点,左子树为空,右子树不为空

4、树中存在删除结点,左子树不为空,右子树为空

5、树中存在删除结点,左右子树均不为空

这里我们主要有递归法迭代法

1. 递归法 

1.1 普通二叉树

对于普通的二叉树来说,这里采用了交换数值的方法来最终删除目标结点,首先当第一次遇到所要删除的结点时,如果其右子树存在,那么找到该右子树的最左结点,将两个结点值进行交换,这是第一次遇到的时候的处理方法;后面再遇到时,只要其右子树不为空,就按照上面的方法继续交换,知道再遇到该删除结点时,其右子树为空了,那么就将左子树返回,其实这时候是将NULL覆盖了要删除的值,并返回了NULL,将其删除了。

TreeNode* deleteNode(TreeNode* root, int key) {
        if(root == NULL) return NULL; //未找到结点
        if(root -> val == key){
            if(root -> right == NULL) return root -> left;
            //也就是说,此时该结点不存在右子树了,直接返回其左子树即可

            TreeNode* tmp = root -> right;
            while(tmp -> left){
                tmp = tmp -> left;
            }
            swap(tmp -> val, root -> val);
            //这里是找到该结点的右子树的最左结点,将值进行交换,方便最后进行删除
        }
        root -> left = deleteNode(root -> left, key);
        root -> right = deleteNode(root -> right, key);
        return root;
    }

1.2 二叉搜索树

对于二叉搜索树来说,因为其有序,所以最后会根据结点大小来定向,也就是说不会遍历整棵树,而是有选择地遍历相关路径的树。

当遇到第一种情况时,直接返回NULL就行;当遇到第二种情况时,也返回NULL,因为是根节点,直接删除即可,删除了就是NULL了;对于第三和第四种情况,一方为空,一方不为空,返回不为空的一方作为新的根节点,然后删除结点即可;对于第五种情况,首先是找到删除结点的右子树,然后找到该右子树的最左结点,将删除结点的左子树整体迁移到该地方,然后返回删除结点的右子树作为新的根节点。以上是当遇到了删除结点时的处理方法。

    TreeNode* deleteNode(TreeNode* root, int key) {
        //第一种情况,结点不存在
        if(root == NULL) return NULL;//未找到相应元素或者说root本身未空
        if(root -> val == key){
            //第二种情况,结点的左右子树为空
            if(root -> left == NULL && root -> right == NULL){
                delete root;
                return NULL;
            }else if(root -> left == NULL){
            //第三种情况,结点的左子树为空,右子树不为空
                TreeNode* tmp_root = root -> right;
                delete root;
                return tmp_root;
            }else if(root -> right == NULL){
            //第四种情况,结点的右子树为空,左子树不为空
                TreeNode* tmp_root = root -> left;
                delete root;
                return tmp_root;
            }else{
                TreeNode* tmp = root -> right;
                while(tmp -> left != NULL){
                    tmp = tmp -> left;//找到右子树的最左结点位置
                }
                tmp -> left = root -> left;//将结点的左子树全部转移到右子树的最左结点位置;
                TreeNode* tmp_ = root;//准备删除root
                root = root -> right;
                delete tmp_;
                return root;
            }
        } 
        if(root -> val > key) root -> left = deleteNode(root -> left, key);
        if(root -> val < key) root -> right = deleteNode(root -> right, key);
        return root;
    }

2. 迭代法

迭代法的主要思想是和二叉搜索树递归的时候的思路是一样的,这里实现了一个函数deleteOneNode专门处理遇到删除结点时的各种情况,这里对处理情况进行了一个精化。然后在主函数中我们使用了pre指针指向要删除结点的父节点,主要是为了判断删除的是左结点还是右结点,这样方便进行接收。

    TreeNode* deleteOneNode(TreeNode* root){
        if(root -> right == NULL) return root -> left;
        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 == NULL) return NULL;
        TreeNode* cur = root;
        TreeNode* pre = NULL;//设置父节点
        while(cur != NULL){
            if(cur -> val == key){
                break;
            }
            pre = cur;
            if(cur -> val > key) cur = cur -> left;
            else cur = cur -> right;
        }
        if(pre == NULL){
            //头节点为需要删除的结点
            return deleteOneNode(root);
        }
        //这里需要清楚删除的是pre的左结点还是右结点
        if(pre -> left && pre -> left -> val == key){
            pre -> left = deleteOneNode(pre -> left);
        }
        if(pre -> right && pre -> right -> val == key){
            pre -> right = deleteOneNode(pre -> right);
        }
        return root;
    }

感谢你的阅读,希望我的文章能够给你帮助,如果有帮助,麻烦点赞加收藏,或者点点关注,非常感谢。

如果有什么问题欢迎评论区讨论!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值