算法刷题 - 二叉树 (验证二叉搜索树 + 二叉搜索树中的最小差值 + 众数 + 公共祖先 +插入操作)

127 阅读7分钟

算法刷题博客封面.png

简介

题目 - 01: 验证二叉搜索树 (LeetCode 98)

题目 - 02: 二叉搜索树中的最小绝对值 (LeetCode 530)

题目 - 03: 二叉搜索树中的众数 (LeetCode 501)

题目 - 04: 二叉树的最近公共祖先 (LeetCode 236)

题目 - 05: 二叉搜索树的插入操作 (LeetCode 701 )

题目 01 - 验证二叉搜索树

原题链接

LeetCode 98 - 验证二叉搜索树

题目描述

给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树 有效 二叉搜索树定义如下:

  • 节点的左子树只包含 小于 当前节点的数。
  • 节点的右子树只包含 大于 当前节点的数。
  • 所有左子树和右子树自身必须也是二叉搜索树。

方法 01 - 递归 + 转化成数组

  • 如果为一个二叉搜索树,则按照中序遍历 (左根右)得到的结果应该是递增的
  • 可以先遍历并按照中序遍历记录所有遍历得到的节点值- 然后判断是否递增,如果不是则不是二叉搜索树

代码实现

class Solution {

    private List<Integer> ans = new ArrayList<>();

    public boolean isValidBST(TreeNode root) {
        if (root == null) {
            return false;
        }
        inOrder(root);
        for (int i = 1; i < ans.size(); i++) {
            if (ans.get(i - 1) >= ans.get(i)) {
                return false;
            }
        }
        return true;
    }

    public void inOrder(TreeNode node) {
        if (node == null) {
            return;
        }
        inOrder(node.left);
        ans.add(node.val);
        inOrder(node.right);
    }
}

执行结果

  • 时间复杂度: O(N)
  • 空间复杂度: O(N)

方法 02 - 递归内判断

  • 直接再递归内判断:
  • 验证条件: 左子树中所有节点均要小于中间节点,右子树所有节点大于中间节点
  • 按照中序遍历顺序,后一个遍历到的节点值一定要比前一个节点值大 prev
  • 临时变量用于记录前一个结点

代码实现

class Solution {
    TreeNode pre = null;

    public boolean isValidBST(TreeNode root) {
        if (root == null) {
            return true;
        }
        // 中序遍历: 左
        boolean isLeftValid = isValidBST(root.left);
        // 若为二叉搜索树前一个节点的值需要小于后一个
        if (pre != null && pre.val >= root.val) {
            return false;
        }
        pre = root; // 更新前一个结点的记录
        boolean isRightValid = isValidBST(root.right);
        return isLeftValid && isRightValid;
    }
}

执行结果

  • 时间复杂度: O(N)
  • 空间复杂度: O(H)

题目 02 - 二叉搜索树的最小绝对值

原题链接

LeetCode 530 - 二叉搜索树的最小绝对值

题目描述

给你一个二叉搜索树的根节点 root ,返回 树中任意两不同节点值之间的最小差值。

差值是一个正数,其数值等于两值之差的绝对值。

方法 - 递归

  • 借助两个变量分别记录,最小差值: midDiff; 前一个遍历到的节点: 最小差值。
  • 由于是二叉搜索树,如果按照中序遍历的话。为递增序列,因此最小差值肯定发生在两个相邻节点之间
  • 借助 abs 计算最小差值。 Math.min() 更新最小差值
  • 每轮遍历完当前节点后更新前驱节点 pre 指向当前节点

代码实现

class Solution {

    private TreeNode pre;
    private int minDiff = Integer.MAX_VALUE;

    public int getMinimumDifference(TreeNode root) {
        if (root == null) {
            return 0;
        }
        if (root.left == null && root.right == null) {
            return root.val;
        }
        getMin(root);
        return minDiff;
    }

    public void getMin(TreeNode node) {
        if (node == null) {
            return;
        }
        getMin(node.left); // 中序遍历; 左
        if (pre != null) { // 中
            minDiff = Math.min(minDiff, Math.abs(pre.val - node.val));
        }
        pre = node; //更新前驱
        getMin(node.right); // 右
    }
}

执行结果

  • 时间复杂度: O(N)
  • 空间复杂度: O(H)

题目 03 - 二叉搜索树中的众数

原题链接

LeetCode 501 - 二叉搜索树的众数

题目描述

给你一个含重复值的二叉搜索树(BST)的根节点 root ,找出并返回 BST 中的所有 众数(即,出现频率最高的元素)。

如果树中有不止一个众数,可以按 任意顺序 返回。

假定 BST 满足如下定义: 结点左子树中所含节点的值 小于等于 当前节点的值 结点右子树中所含节点的值 大于等于 当前节点的值 左子树和右子树都是二叉搜索树

方法 - 递归

  • 由于是二叉搜索树,如果按照中序遍历,则遍历的顺序得到的结果为从小到大
  • prev 记录当前节点的前驱节点,count 记录当前节点值的出现次数,maxCount 记录最大出现次数。
  • 如果 prev 为空或者当前节点值与前驱节点值不一致,则需要更新计数 count
  • 将等于 count 出现次数的所有节点值记录,假设它们都为 '众数';
  • 若存在 count > maxCount,说明众数出现次数大于当前节点值出现次数,清空之前记录的所有 '众数',
  • 记录当前 curr.val 为众数之一,同时更新 maxCount 只有大于等于该 curr.val 才可能是众数

代码实现

class Solution {

    private List<Integer> ans = new ArrayList<>(); // 统计所有出现次数最多次元素

    private int maxCount = 0; // 最大出现频率

    private int count = 0; // 出现频率

    private TreeNode prev = null; // 前驱节点

    public int[] findMode(TreeNode root) {
        if (root.left == null && root.right == null) {
            return new int[] { root.val };
        }
        inOrder(root);
        // 将结果封装到数组
        int[] arr = new int[ans.size()];

        for (int i = 0; i < ans.size(); i++) {
            arr[i] = ans.get(i);
        }

        return arr;
    }

    public void inOrder(TreeNode curr) {
        if (curr == null) {
            return;
        }
        inOrder(curr.left);

        int rootValue = curr.val;
        // 计数,如果前驱为 null 或者遍历到新节点值
        if (prev == null || rootValue != prev.val) {
            count = 1;
        } else {
            count++;
        }
        // 更新结果以及maxCount
        if (count > maxCount) {
            ans.clear();
            ans.add(rootValue);
            maxCount = count;
        } else if (count == maxCount) {
            ans.add(rootValue);
        }
        prev = curr;

        inOrder(curr.right);
    }
}

执行结果

  • 时间复杂度: O(N)
  • 空间复杂度: O(H)

题目 04 - 二叉树的最近公共祖先

原题链接

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

题目描述

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

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,

最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

方法 - 递归

若存在祖先节点:

  1. 情况一: 如果当前 p q 分别为一个 node 的左/右子节点则返回 node
  2. 情况二: 如果当前 p q 中的一个为另外一个的祖先节点,则返回 p / q
  • 由于需要判断左右子树的父节点,那么只能通过后序遍历实现从底向上的遍历方式
  • 在回溯的过程中,需要遍历整个二叉树
  • 归返回条件,如果为空,或者当前节点 node 为 q 或者 p 也返回
  • 借助返回值判断
    • 如果返回值都为 NULL,叶子节点
    • 如果返回值不为,即为 p 和 q; 此时祖先节点即为当前层的 root 节点;返回 root
    • 如果返回值其中一个为 NULL,另外一个不为 NULL 的即为公共祖先结果,传递回上一层

代码实现

class Solution {

    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if(root == q || root == p || root == null) return root;
        //遍历左子树
        TreeNode left = lowestCommonAncestor(root.left, p, q);
        //右子树
        TreeNode right = lowestCommonAncestor(root.right, p, q);
        //如果 left 和 right 都不为空,说明 root 就是公共节点
        if(left != null && right != null) {
            return root;
        }
        //若存在不为空,已找到最近公共祖先 / 当前p或者q为另外一个的祖先节点,返回
        if(left == null && right != null) {
            return right;
        } if(right == null && left != null) {
            return left;
        }
        //否则返回 null
        return null;
    }
}

执行结果

  • 时间复杂度: O(N)
  • 空间复杂度: O(H)

题目 05 - 二叉搜索树中的插入操作

原题链接

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

题目描述

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

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

方法 - 递归

  • 仅考虑挂载节点的情况,不去实现重构整个树结构
  • 递归终止条件 node == null: 插入树节点 new TreeNode(root.val);
  • 由于是二叉搜索树,如果 val > root.val 当前节点值,查询其右子树; 反之左子树
  • 传递参数:node 的左右子直接 + val
  • 挂载新节点的方式: node.left = return 树节点

代码实现

class Solution {

    public TreeNode insertIntoBST(TreeNode root, int val) {
        if(root == null) {
            return new TreeNode(val);
        }
        //当前节点大于 val; 往左子树寻找;反之右子树
        if(root.val > val) root.left = insertIntoBST(root.left, val);   //如果节点为空,挂载节点
        if(root.val < val) root.right = insertIntoBST(root.right, val); 
        return root;
    }
}

执行结果

  • 时间复杂度: O(N)
  • 空间复杂度: O(H)