【代码训练营】day 21 235. 二叉搜索树的最近公共祖先 & 701.二叉搜索树中的插入操作 & 450.删除二叉搜索树中的节点

本文介绍了如何利用二叉搜索树的特性来解决LeetCode中的相关问题,包括找到最近公共祖先的两种方法,插入新节点的两种迭代和递归策略,以及删除节点的逻辑分析和处理。关键在于理解二叉搜索树的有序性并有效地利用其结构进行操作。
摘要由CSDN通过智能技术生成

所用代码 java

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

题目链接:二叉搜索树的最近公共祖先 LeetCode 235 - 中等

思路

只要昨天的求普通二叉树的最近公共祖先会了,这道题就不难了。重要的是我们如何利用二叉搜索树的性质去寻找最近的公共祖先。

普通二叉树版:后序

  1. 返回逻辑:root为空,或root找到其中的一个子节点,返回root – 其中一个结点时祖先结点和这种情况一样
  2. 左右递归
  3. 中处理逻辑:root的左右子树不为空,返回 root – 证明 左右子树存在p和q,root就为祖先结点
  4. 一边为空,另一边不为空,返回不会空的结点 – 证明某一条边有p或q
  5. 都为空,证明孩子无目标结点 return null
class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        return traversal(root, p, q);
    }public TreeNode traversal(TreeNode root, TreeNode p, TreeNode q){
        if (root == null) return null;
        // 若其中一个父节点是祖先结点的话,就相当于直接返回祖先
        if (root == p || root == q) return root;TreeNode left = traversal(root.left, p, q);
        TreeNode right = traversal(root.right, p, q);// 中 - 处理逻辑
        if (left != null && right != null) return root;
        if (left != null && right == null) return left;
        else if (left == null && right != null) return right;
        else return null;
    }
}

利用二叉搜索树的性质:值左小右大

不涉及到遍历顺序,因为二叉树有序,也不需要对二叉树进行处理,只需要有左和右遍历就行了

  1. 判断root.val与p和q值大小关心
  2. 根小于root.val就往右递归,大于就往左
  3. 最后剩下的就是等于,或者两者在中间的情况,此时root就是他们的祖先结点
class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        return traversal(root, p, q);
    }public TreeNode traversal(TreeNode root, TreeNode p, TreeNode q){
        if (root == null) return null;
        // 如果p和q的值都小于root,证明都在左边
        if (p.val < root.val && q.val < root.val) {
            TreeNode left = traversal(root.left, p, q);
            if (left != null) return left;
        }
        // 如果p和q的值都大于root,证明都在右边
        if (p.val > root.val && q.val > root.val) {
            TreeNode right = traversal(root.right, p, q);
            if (right != null) return right;
        }
        // 若p或q在左右两边 或者等于root
        if (p.val <= root.val && q.val >= root.val){
            return root;
        }
        if (p.val >= root.val && q.val <= root.val){
            return root;
        }
        return null;
    }
}

上面代码可以简化

class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        return traversal(root, p, q);
    }public TreeNode traversal(TreeNode root, TreeNode p, TreeNode q){
        if (root == null) return null;
        // 如果p和q的值都小于root,证明都在左边  -- 左
        if (p.val < root.val && q.val < root.val) {
            TreeNode left = traversal(root.left, p, q);
            if (left != null) return left;
        }
        // 如果p和q的值都大于root,证明都在右边  -- 右
        if (p.val > root.val && q.val > root.val) {
            TreeNode right = traversal(root.right, p, q);
            if (right != null) return right;
        }
        // 剩下的情况就是在中间的,或者p或q本身就是root
        return root;
    }
}

迭代法: 二叉搜索树的迭代法非常简单

while里面要写if else if else才行,因为root进行向下迭代的时候,只会有一种情况

class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        while (root != null){
            // root的值比q和p都大,就往左搜索 ,反之往右
            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;
            // 否则就是在两边或者p、q其中一个等于root的情况
            // 就直接返回root,因为此时root就为祖先
            else return root;
        }
        // 最后就是root=null的情况
        return root;
    }
}

总结

利用好二叉搜索树的性质,因为他的有序性,递归的时候没有中结点的处理逻辑,迭代的时候也是一步到位。

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

题目链接:二叉搜索树中的插入操作 LeetCode 701 - 中等

思路

比较根结点与要插入值的大小关系,判断应该往哪边插入值。

且插入的值都可以在叶子结点插入没有考虑左右是否平衡

  1. 比较val与根结点(node)值大小关系
  2. node更小,就把判断node.left是不是为空,为空就把val挂在node的左孩子上,不为空就继续往左搜索
  3. 往右同理
  4. 最后会出现val挂在叶子结点上,node.val = val 证明插入成功,就返回

迭代法:

class Solution {
    public TreeNode insertIntoBST(TreeNode root, int val) {
        TreeNode node = new TreeNode(val);
        if (root == null) return node;
        // 用一个指针指向插入的位置
        TreeNode pre = root;
        while (true){
            // val值大于根结点的值,就往右
            if (pre.val < val && pre.right != null) {
                pre = pre.right;
            }else if (pre.val < val && pre.right == null){
                pre.right = node; // 把值挂在叶子上
            // val值小于根结点的值,就往左
            }else if (pre.val > val  && pre.left != null){
                pre = pre.left;
            }else if (pre.val > val  && pre.left == null){
                pre.left = node;  // 把值挂在叶子上
            }else break; // pre.val = node.val
        }
        return root;
    }
}

迭代还可以双指针,使其操作没那么复杂

class Solution {
    public TreeNode insertIntoBST(TreeNode root, int val) {
        TreeNode node = new TreeNode(val);
        if (root == null) return node;
        TreeNode pre = null;
        TreeNode cur = root;
        // 找到待插入的叶子结点,
        // 当cur==null退出,则pre刚好是待插入的叶子结点
        while (cur != null){
            pre = cur;
            if (cur.val > val) cur = cur.left;
            else cur = cur.right;
        }// 判断该值的插入位置(左还是右)
        if (pre.val > val) pre.left = node;
        else pre.right = node;return root;
    }
}

递归法:

  1. 当我们搜索到空结点,这个空结点就是应该插入的值
  2. 把这个值返回上去
  3. 此时根结点的左孩子或者右孩子就把该值给接收了
  4. 最后返回的root就是构建好的二叉树
class Solution {
    public TreeNode insertIntoBST(TreeNode root, int val) {
        return insert(root, val);
    }
    
    public TreeNode insert(TreeNode root, int val){
        // 若遍历到了空结点(叶子结点的下一层),就把node赋值给空结点并返回
        if (root == null){
            TreeNode node = new TreeNode(val);
            // 返回node给root的左或右孩子去接受、相当于插入
            return node;
        }
        
        // 这里就 root 的左右孩子就用来接收下层传来的已经插入好的二叉树
        if (root.val > val){
            root.left = insert(root.left, val);
        }
        if (root.val < val){
            root.right = insert(root.right, val);
        }
        
        // 最后返回根结点,此时根结点的左右孩子已经完成插入
        return root;
    }
}

递归还有一种无返回值(双指针)的写法,这种写法就需要定义一个指针指向root的前一个结点

class Solution {
    TreeNode pre = new TreeNode(-1);
    public TreeNode insertIntoBST(TreeNode root, int val) {
        if (root == null) return new TreeNode(val);
        insert(root, val);
        return root;
    }public void insert(TreeNode cur, int val){
        if (cur == null){
            TreeNode node = new TreeNode(val);
            // pre 跟着cur一路到叶子结点
            // 此时pre是叶子结点,cur到了null
            // pre就是上一个结点,就用于判断node应该插入在左还是右
            if (val > pre.val) pre.right = node;
            else pre.left = node;
            return;
        }// 用于赋值将pre指向cur已经走过的路
        pre = cur;if (cur.val > val) insert(cur.left, val);
        if (cur.val < val) insert(cur.right, val);return;
    }
}

总结

这题我们可以取巧把待插入结点放在叶子结点,就可以递归或者迭代将结点直接插入就可以了,若是不插入在叶子结点,使树为平衡二叉树的话就比较难。

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

题目链接:删除二叉搜索树中的节点 LeetCode 450 - 中等

思路

无。


最重要的需清楚删除结点的位置,然后应该怎样删除,据此共有5中情况:

  1. 没有找到需要删除的结点,返回null

  2. 待删除的结点:是叶子结点 – 直接把该结点删除,向上返回null

  3. 左子树为空,右子树不为空 – 将右子树给返回

  4. 左子树不为空,右子树为空 – 将左子树给返回

  5. 左右子树都不为空,就需要重新构建二叉树:

    1. 连接左子树,右子树值都大于左子树,所有把右子树挂在左子树的最右边的结点
    2. 或者 – 连接右子树,由于左子树的值都小于右子树,所以把左子树的挂在右子树最左边的结点上
class Solution {
    public TreeNode deleteNode(TreeNode root, int key) {
        return delete(root, key);
    }public TreeNode delete(TreeNode root, int key){
        // 没找到
        if (root == null) return null;
        // 找到了的逻辑
        if (root.val == key) {
            // 第一种情况:删叶子结点
            if (root.left == null && root.right == null) return null;
            // 第二种情况:左子树为空,右子树不为空,直接将右子树返回
            else if (root.left == null && root.right != null) return root.right;
            // 第三种情况:左子树不为空,右子树为空,将左子树返回
            else if (root.left != null && root.right == null) return root.left;
            // 最后一种情况:左右都不为空,删的是中间结点,需重建二叉搜索树
            else {
                // 以左子树构建 - 找到最右边的结点,并把右子树挂在最后
                TreeNode cur = root.left;
                while (cur.right != null){
                    cur = cur.right;
                }
                cur.right = root.right;
                // 返回以左子树新构建的二叉搜索树
                return root.left;
            }
        }// 二叉搜索树的递归方法,判断往左还是往右
        // 根结点的左右子树接收前面递归传回来的已构建好的二叉树
        if (root.val > key) root.left = delete(root.left, key);
        if (root.val < key) root.right = delete(root.right, key);return root;
    }
}

通用的二叉树删除结点方式:

  1. 找到待删除结点
  2. 将他与左或者右叶子结点交换(或者将该叶子结点值赋给该结点)
  3. 删除叶子结点
if (root.val = key){
    if (root.left == null) return right;
    if (root.right == null) return left;
    // 以往右为例
    TreeNode node = root.right;
    while (node.right != null){
        node = node.right;
    }
    // node就指向了最右边的叶子结点,再把叶子结点的值赋给该结点
    root.val = node.val;
    // 再继续向右搜索删除该叶子结点,且删除的是叶子结点的值
    root.right = delete(root, node.val);
}

总结

二叉搜索树递归不用分前中后序,因为他本身就可以看作是排好序的二叉树,且中序遍历的值是升序的。

在解决二叉搜索的时候,大体按以下思路操作

public TreeNode traversal(TreeNode root, int key){
    // 1、返回逻辑
    if (root == null) 返回情况;
    其余的返回情况,全部列举
    if...
    if...
    // 2、递归逻辑      
        // 只用考虑左方向 -- 并接收左孩子传来的结果
    if (root.val > key) root.left = traversal(root.left, key); 
        // 右方向同理
    if (root.val < key) root.right = traversal(root.right, key);
    // 3、最后递归函数返回值
        // 通常返回处理好的结果root
    return root;
    
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值