【代码训练营】day20 | 530.二叉搜索树的最小绝对差 & 501.二叉搜索树中的众数 & 236. 二叉树的最近公共祖先

文章介绍了如何利用中序遍历解决二叉搜索树中找到最小绝对差的问题,提供了递归和迭代两种解法。此外,还讲解了在二叉搜索树中找众数的思路,同样应用了中序遍历并采用递归和迭代实现。这些方法都利用了二叉搜索树节点值的单调性来优化算法。
摘要由CSDN通过智能技术生成

所用代码 java

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

题目链接:二叉搜索树的最小绝对差 LeetCode 530 - 简单

思路

这个题和昨天的验证二叉搜索树时一样的,中序遍历的二叉搜索树,他的结点是单调递增的,所以可以用一个指针指向前一个结点,用现结点的值减前向结点的值。

递归法:

class Solution {
    int min = Integer.MAX_VALUE;
    // 用来指向二叉搜索树的前一个结点
    TreeNode pre = null;
    public int getMinimumDifference(TreeNode root) {
        traversal(root);
        return min;
    }// 二叉搜索树一般中序遍历
    public void traversal(TreeNode root){
        if (root == null) return;
        // 左
        traversal(root.left);
        
        // 中 - 后一个结点值减前一个结点值便是最小值
        if (pre != null && root.val - pre.val < min){
            min = root.val - pre.val;
        }
        // 当 root=null, return
        // 当 root=1, pre=null
        // 继续递归 root=2, pre=1
        pre = root;
//        System.out.println("min = " + min);
        // 右
        traversal(root.right);
    }
}

迭代法:

class Solution {
    public int getMinimumDifference(TreeNode root) {
        if (root == null) return 0;
        Stack<TreeNode> stack = new Stack<>();
        TreeNode pre = null;
        int min = Integer.MAX_VALUE;
        stack.push(root);
        // 中序:左 中 右
        while (!stack.isEmpty()) {
            // 要把访问过的结点先拿出来
            TreeNode node = stack.pop();
            if (node != null) {
                if (node.right != null) stack.push(node.right);
​
                stack.push(node);
                stack.push(null); // 存入标记if (node.left != null) stack.push(node.left);
            } else {
                // 取出除null的最上层结点
                node = stack.pop();
                if (pre != null && node.val - pre.val < min){
                    min = node.val - pre.val;
                }
                pre = node;
            }
        }
        return min;
    }
}

总结

只要知道中序遍历二叉搜索树是一个单调递增的结果,那么就可以直接把结果保存在一个list里面进行各种操作,进阶一点就是直接再遍历的时候进行操作,最后就是双指针操作前一个结点和后一个结点

双指针逻辑:

if (pre != null && 操作逻辑){
    操作逻辑
}
// 每次操作了之和把该结点赋值给pre,然后node回溯(或者往右搜索右子树)
pre = node;

二叉搜索树中的众数 LeetCode 501

题目链接:二叉搜索树中的众数 LeetCode 501 - 简单

思路

一种方法就是中序遍历所有结果存入list,再遍历,遇到相同的结果就拿出 - 暴力

另一个就是递归,同样遇到相同的结点就存入


  1. pre=null时,cur遍历到了左子树的叶子结点,此时cur所出现的次数为1
  2. 若pre.val=cur.val时,证明前向结点值后向结点值相等,即cur所出现次数+1
  3. 否则pre.val!=cur.val,证明前向结点值和后向结点值不等,即cur出现1次
  4. 判断该结点所出现的次数是否等于maxCount,是的话就加入list
  5. 若不是则更新maxCount,以及更新list
class Solution {
    TreeNode pre = null;
    int count = 0;
    int maxCount = 0;
    List<Integer> list = new ArrayList<>();
    public int[] findMode(TreeNode root) {
        traversal(root);
        // 将list转换为int[]数组
        // return res.stream().mapToInt(Integer::intValue).toArray(); 这个效率慢一点
        int[] res = new int[list.size()];
        int index = 0;
        for (Integer i : list) {
            res[index++] = i;
        }
        return res;
    }public void traversal(TreeNode cur){
        if (cur == null) return;
        traversal(cur.left);// 中 - 处理逻辑
        if (pre == null) count = 1;
        else if(pre.val == cur.val) count++;
        else count = 1; // 前向结点值和当前结点值不等,当前结点就只出现了一次
        // 更新pre
        pre = cur;
        // 添加进list的逻辑
        if (count == maxCount) list.add(cur.val);
        if (count > maxCount) {
            maxCount = count;
            list.clear();
            list.add(cur.val);
        }
​
​
        traversal(cur.right);
    }
}

迭代法:

class Solution {
    public int[] findMode(TreeNode root) {
        if (root == null) return new int[]{0};
        List<Integer> list = new ArrayList<>();
        int count = 0;
        int maxCount = 0;
        Stack<TreeNode> stack = new Stack<TreeNode>();
        TreeNode cur = root;
        TreeNode pre = null;
        while (cur != null || !stack.isEmpty()){
            if (cur != null){
                stack.push(cur);
                cur = cur.left; // 左
            }else {
                // 中
                TreeNode node = stack.pop();
                if (pre == null || pre.val != node.val) count = 1;
                else if (pre.val == node.val) count ++;
                // 更新pre,使其一直在node的前面
                pre = node;
                if (count == maxCount) list.add(node.val);
                if (count > maxCount){
                    maxCount = count;
                    list.clear();
                    list.add(node.val);
                }
​
                cur = node.right; // 右
            }
        }
        // 用stream流的方式比直接new一个数组再赋值要慢
        return list.stream().mapToInt(Integer::intValue).toArray();
    }
}

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

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

思路

无。


二叉树要从下往上遍历,需要用他的回溯过程去实现,而要在回溯中达到这种效果就需要用后序遍历 - 左 右 中

情况1:p 和 q 的祖先需往上搜索

情况2:p 和 q 某一个结点本身就是祖先

  1. 首先返回条件:遍历到空结点 返回null
  2. 当遇到p或q的其中一个结点,直接返回该结点
  3. 左右递归
  4. 中,接到左右递归传来的值进行处理
  5. 中 - 左右子树都不为null,则左右子树分别存在一个p或p,就返回 中 ,此时中为祖先
  6. 中 - 左为空,右不为空,就向上返回 右;反之左不为空,右为空,就返回 左
  7. 中 - 左右都为空,则中的孩子没有p或q,就返回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 root;
        // 当遍历都某一结点时刚好时p或q,就把这个结点返回
        // 此时会报情况2给包含,因为遇到祖先(如p),则返回p(root),后面就会一直返回下层传来的祖先p
        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;// 只有根结点的左边包含p或q,则返回左子树
        if (left != null && right == null) return left;
        // 只有右边有
        else if (left == null && right != null) return right;
        // 左右都没
        else return null;
    }
}

总结

二叉树进行递归时,对于有没有返回值就是要看我们是不是要处理一整条边,需注意返回之后应该如何处理。另外对于需要从底向上进行操作的二叉树一般都是后序遍历,因为后序遍历存在着由底向上的回溯过程,这个回溯过程就可以看做从下到上的递归逻辑。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值