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

91 阅读7分钟

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

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

235. 二叉搜索树的最近公共祖先 - 力扣(Leetcode)

思路

本题与昨天(day21)的最后一题 236.二叉搜索树的最近公共祖先相比,多了一个限制条件:二叉搜索树。
可以利用二叉搜索树的特性对昨天的算法进行剪枝。该特性是:

对于二叉搜索树的每一个节点,该节点的左子树上的每一个节点(如果存在)都小于该节点的值,该节点的右子树上的每一个节点(如果存在)都大于该节点的值。 且二叉搜索树的每一棵子树都是二叉搜索树。

使用左右中顺序的后序遍历方式,寻找目标的两个节点,由于二叉搜索树中每个节点的值不一样,这道 题可以通过值比较的方式寻找节点。

对于二叉树的一个节点root,pq中较小值记为low,较大值记为high

  • 如果当前节点就是目标节点,或者是null,直接返回当前节点。
  • 如果当前节点的值root.val大于high,那么目标节点只可能出现在当前节点的左子树上,因此返回递归遍历左子树的结果即可。
  • 如果当前节点的值root.val小于low,那么目标节点只可能出现在当前节点的右子树上,因此返回递归遍历右子树的结果即可。
  • 如果当前节点的值root.val小于high 并且大于 low,那么值较小的节点出现在当前节点的左子树上,较大的节点出现在右子树上,因此,当前节点就是公共祖先,且是最近公共祖先,返回当前节点即可。

代码

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */

class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {

        int low = p.val > q.val ? q.val : p.val;
        int high = p.val > q.val ? p.val : q.val;
        
        return getLowest(root, low, high);
        
    }

    private TreeNode getLowest(TreeNode root, int low, int high){
        if(root == null || root.val == low || root.val == high){
            return root;
        }

        if(root.val > high){
            return getLowest(root.left,low,high);
        }

        if(root.val < low){
            return getLowest(root.right, low, high);
        }
        
        return root;

    }
}

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

给定二叉搜索树(BST)的根节点 root 和要插入树中的值 value ,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 输入数据 保证 ,新值和原始二叉搜索树中的任意节点值都不同。 注意,可能存在多种有效的插入方式,只要树在插入后仍保持为二叉搜索树即可。 你可以返回 任意有效的结果 。

提示:

  • 树中的节点数将在 [0, 104]的范围内。
  • -108 <= Node.val <= 108
  • 所有值 Node.val 是 独一无二 的。
  • -108 <= val <= 108
  • 保证 val 在原始BST中不存在。

701. 二叉搜索树中的插入操作 - 力扣(Leetcode)

思路

  • 如果当前节点为 null,那么当前要插入的节点就是二叉树的第一个节点,直接插入即可。
  • 否则,判断当前节点nodeval之间的大小关系。
    • 如果node.val > val,那么新节点要插入到当前节点的左子树中,
      • 如果当前节点的左子树为null,那么新节点就插入到当前节点的左孩子的位置。
      • 否则,将当前节点置为当前节点的左孩子,重复当前操作。
    • 如果node.val < val,那么新节点要插入到当前节点的右子树中,
      • 如果当前节点的左子树为null,那么新节点就插入到当前节点的右孩子的位置。
      • 否则,将当前节点置为当前节点的右孩子,重复当前操作。

代码

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public TreeNode insertIntoBST(TreeNode root, int val) {
        TreeNode node = root;

        if(root == null){
            root = new TreeNode(val);
            return root;
        }

        while(node != null){

            if(node.val > val){
                if(node.left == null){
                    node.left = new TreeNode(val);
                    break;
                }
                
                node = node.left;
                continue;
            }

            if(node.val < val){
                if(node.right == null){
                    node.right = new TreeNode(val);
                    break;
                }

                node = node.right;              
            }

        }


        return root;
    }
}

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

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

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

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

450. 删除二叉搜索树中的节点 - 力扣(Leetcode)

思路

删除二叉搜索树只有两个步骤,但过程中却会遇到多种情况,需要分类讨论。

  • 二叉树是空树,那么无法对空树进行删除,返回null
  • 要删除的节点是根节点,
    • 如果根节点是一个叶子节点,那么删除后二叉树变成一棵空树,返回null
    • 如果根节点同时存在左右子树,那么,要找到右子树中最小值的节点替代要删除节点。这个节点就是根节点的右子树中最左的节点。有以下几种情况:
      • 如果根节点的右孩子只有右子树,没有左子树,那么根节点的右子树中最小值就是这个节点的右孩子,直接将根节点的左子树作为右孩子的左子树,修改后二叉树的根节点是原根节点的右孩子。如图。 二叉树删除节点-根节点01.jpg
      • 如果根节点的右孩子同时有左右子树,那么右子树的最小值出现右孩子的左子树中,找到这个节点node,已知node没有左子树,根据其 有无右子树 分成两种情况:
      • node没有右子树,如图。将node的父节点的左孩子(即node节点)设置为null,将node的值设置到root节点中。 二叉树删除节点-根节点04.jpg
      • node有右子树,如图。将将node的父节点的左孩子(即node节点)设置为node的右孩子,将node的值设置到root节点中。 二叉树删除节点-根节点05.jpg
    • 如果根节点只有左子树,那么删除后,二叉树的根节点变成其左孩子; 二叉树删除节点-根节点03.jpg
    • 如果根节点只有右子树,那么删除后,二叉树的根节点变成其右孩子; 二叉树删除节点-根节点02.jpg
  • 要删除的节点node不是根节点,
    • 首先要找到node的父节点parent,以及标识node是不是parent的左孩子 isLeftChild
    • 如果node是一个叶子节点,那么直接删除该节点,即让node的父节点中node的位置设置为null
    • 如果node同时存在左右子树,那么,要找到右子树中最小值的节点替代要删除节点。这个节点就是node的右子树中最左的节点。有以下几种情况:
      • 如果node的右孩子只有右子树,没有左子树,那么node的右子树中最小值就是这个节点的右孩子,直接将node的右孩子替代node的位置;
      • 否则,找到node右子树中最左边的节点,将该节点的数值替换到node中。如果该节点没有右子树,则将其直接删除;否则,将其右孩子替换到node的位置。

代码

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public TreeNode deleteNode(TreeNode root, int key) {

        if(root == null){
            return null;
        }

        // 如果要删除的是根节点
        if(root.val == key){

            if(root.left == null && root.right == null){
                return null;
            }

            if(root.left != null && root.right == null){
                // 只有左子树
                return root.left;
            }

            if(root.left == null && root.right != null){
                // 只有右子树
                return root.right;
            }

            // 左右子树都有
            if(root.right.left == null){
                root.right.left = root.left;
                return root.right;
            }

            modifyByLeftmostNode(root);

            return root;
        }

        // 删除的不是根节点
        

        // 找到节点 node 的父节点
        TreeNode parent = getParentNode(root,key);
        TreeNode node = null;
        boolean isLeftChild = false;

        // 未找要被删除的节点
        if(parent == null){
            return root;
        }

        // 找到被删除的节点
        if(parent.left != null && parent.left.val == key){
            node = parent.left;
            isLeftChild = true;
        }else{
            node = parent.right;
        }

        // 如果 node 是叶子,直接删除
        if(node.left == null && node.right == null){
            if(isLeftChild){
                parent.left = null;
            }else{
                parent.right = null;
            }

            return root;
        }

        // 如果 node 有两棵子树
        // 找到左子树的最右的节点或右子树最左的节点 , 放到当前节点的位置
        if(node.left != null && node.right != null){
            getLeftmostInRightSubTree(parent,node,isLeftChild);
            return root;
        }


        // node 只有一棵子树
        // 将node的子树只接接到node的父节点
        if(node.left != null){
            if(isLeftChild){
                parent.left = node.left;
            }else{
                parent.right = node.left;
            }

            return root;
        }

        if(node.right != null){
            if(isLeftChild){
                parent.left = node.right;
            }else{
                parent.right = node.right;
            }

            return root;
        }
        

        return null;

    }

    private TreeNode getParentNode(TreeNode node,int key){

        while(node != null){
            if((node.left != null && node.left.val == key) || (node.right != null && node.right.val == key)){
                return node;
            }

            if(node.left!= null && node.val > key){
                // key 在左子树中
                node = node.left;
            }else{
                node = node.right;
            }
        }

        return node;
    }

    /**
     * 找到parent的右子树中最左边的节点, 删除,并返回其值
     * parent:当前节点
     */
    private void getLeftmostInRightSubTree(TreeNode grandParent,TreeNode parent,boolean isLeftChild){

        TreeNode node = parent.right;

        if(node.left == null){
            // 要删除节点的右孩子就是最小值
            if(isLeftChild){
                grandParent.left.val = node.val;
                parent.right = node.right;
            }else{
                grandParent.right.val = node.val;
                parent.right = node.right;
            }
            return;
        }

        modifyByLeftmostNode(parent);
        
    }

    private void modifyByLeftmostNode(TreeNode parent){
        TreeNode node = parent.right;

        while(node.left != null){
            // 找到 node.left 是最左边的节点
            if(node.left.left == null){
                int num = node.left.val;
                // 删除 node 的左孩子
                if(node.left.right == null){
                    // 直接删除
                    node.left = null;
                }else{
                    node.left = node.left.right;
                }
                parent.val = num;
                return;
            }
            node = node.left;
        }
    }
}