力扣刷题Day18

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

题目:力扣

刚开始想的是最小绝对差肯定在叶子节点处出现 - 但是其实也不绝对,最小绝对值可能会发生在路径中。

这里要利用二叉树的特性 - 二叉树是有序的,中序遍历可以得到这个有序数组

因此其实最简单的一个想法就是 - 用一个数组储存二叉树中遍历的元素,然后遍历这个数组,判断相邻的元素最小绝对值差。

class Solution {
    
    public int getMinimumDifference(TreeNode root) {
        List<Integer> list = new ArrayList<>();

        Helper(root, list);

        //找最小差值
        int result = Integer.MAX_VALUE;
        for(int i=1; i< list.size(); i++){
            int dis = Math.abs(list.get(i) - list.get(i-1));
            if( dis < result){
                result = dis;
            }
        }

        return result;
        
    }

    //中序遍历
    public void Helper(TreeNode root, List<Integer> list){
        if(root == null){
            return;
        }

        Helper(root.left, list);
        list.add(root.val);
        Helper(root.right, list);
    }

}

当然这个差值比较过程可以在二叉树遍历的时候就进行,一步到位。

这里进行递归的时候就需要储存前一个节点的值。

在进行实现的时候有个小细节要注意:这里的不能用pre进行传参,如果这个辅助方法是:

                Helper(TreeNode root,TreeNode pre)

那么刚开始的时候pre都是null,经过第一个左节点后,pre更新成为了左节点,但是返回之后,pre又变成了原来的null。

因此传参会存在问题。

这里可以把pre当作一个全局变量放在外部。

每次对pre进行更新就可/              

class Solution {
    
    public int getMinimumDifference(TreeNode root) {
        Helper(root);
        return dis;
    }

    //中序遍历
    int dis = Integer.MAX_VALUE;
    TreeNode pre = null;//前一个节点
    public void Helper(TreeNode root){
        if(root == null){
            return;
        }

        Helper(root.left);//左

        //  中间节点
        if(pre != null){
           dis = Math.min(Math.abs(pre.val - root.val), dis); 
        }
        pre = root;
       
        Helper(root.right);//右
    }

}

参考资料:代码随想录

501.二叉搜索树中的众数 

题目:力扣

看到题的第一种想法:

                把整个树遍历一遍,用map来统计数字出现的频率,然后取出频率最高的;

但是这个地方其实是二叉树,可以利用二叉树的特性

        二叉树中序遍历的元素是有序的。

因此相同大小的元素是相邻出现的;可以遍历时直接更新最大频次。

按照最小绝对值的做法,可以将树用中序遍历一遍,取出数组;然后再对数组遍历一遍,找到最大频次的元素。

此时又有问题了,因为要求最大频率的元素集合(注意是集合,不是一个元素,可以有多个众数),如果是数组上大家一般怎么办?

应该是先遍历一遍数组,找出最大频率(maxCount),然后再重新遍历一遍数组把出现频率为maxCount的元素放进集合。(因为众数有多个)

这种方式遍历了两遍数组。

那么我们遍历两遍二叉搜索树,把众数集合算出来也是可以的。

但这里其实只需要遍历一次就可以找到所有的众数。

那么如何只遍历一遍呢?

        如果 频率count 等于 maxCount(最大频率),当然要把这个元素加入到结果集中;

        频率count 大于 maxCount的时候,不仅要更新maxCount,而且要清空结果集(以下代码为result数组),因为结果集之前的元素都失效了。

代码实现:

class Solution {
    public int[] findMode(TreeNode root) {
        List<Integer> list = new ArrayList<>();

        Helper(root, list);

        List<Integer> result = new ArrayList<>();
        int max_count = 0;
        int count = 0;
        int value = -1;

        //只遍历一遍,如果和当前的最大频率相同则加入结果集;如果有发现更大的频率,则清空结果集,重新加入
        for(int i=0; i<list.size(); i++){
            //先计算count
            if(list.get(i) == value){
                count++;
               
            }else{
                count = 1;
                value = list.get(i);
            }

            //再更新结果集
            if(count == max_count){
                result.add(list.get(i));
            }
            if(count > max_count){
                max_count = count;
                result.clear();
                result.add(list.get(i));
            }
        }

        int[] re = new int[result.size()];
        for(int i=0; i<result.size(); i++){
            re[i] = result.get(i);
        }

        return re;
        
    }

    public void Helper(TreeNode root, List<Integer> list){
        if(root == null){
            return;
        }

        Helper(root.left, list);

        list.add(root.val);

        Helper(root.right, list);
    }

}

直接采用中序遍历并且处理最大频次结果集:

class Solution {
    public int[] findMode(TreeNode root) {

        Helper(root);

        int[] re = new int[result.size()];
        for(int i=0; i<result.size(); i++){
            re[i] = result.get(i);
        }

        return re;
        
    }


    TreeNode pre;
    int max_count = 0;
    int count = 0;
    List<Integer> result = new ArrayList<>();
    public void Helper(TreeNode root){
        if(root == null){
            return;
        }

        Helper(root.left);

        //先计算count
        if(pre == null){
            count=1;
        }else if(root.val == pre.val){
            count++; 
        }else{
            count = 1;
        }

        //再更新结果集
        if(count == max_count){
            result.add(root.val);
        }
        if(count > max_count){
            max_count = count;
            result.clear();
            result.add(root.val);
        }

        //更新上一个节点
        pre = root;

        Helper(root.right);
    }

}

参考资料:代码随想录

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

题目:力扣

要找最近公共祖先,第一反应肯定是要自底向上寻找这个节点。

        但是二叉树的构造并没有自底向上这个方向。

然而,二叉树的后序遍历就是自底向上向上的遍历过程。

确定二叉树的遍历顺序之后,需要判断二叉树的最近公共祖先情况。

这道题主要是需要对题干中的条件理解清楚,有些比较复杂的情况,但是因为题干中的限制,其实仍然可以按照一个简单的后序遍历逻辑解决。

情况一:

节点1在左子树,节点2在右子树。

情况二:

递归逻辑:

判断逻辑是 如果递归遍历遇到q,就将q返回,遇到p 就将p返回,那么如果 左右子树的返回值都不为空,说明此时的中节点,一定是q 和p 的最近祖先。

递归步骤:

        1. 输入和输出:输入(根节点,节点q,节点p);输出(公共祖先节点)

        2. 终止条件:若遇到节点q,则返回q;若遇到节点p,则返回p;若节点位空则返回null。

        3. 单层递归逻辑:先向左遍历,获得左子树的返回节点;再向右遍历,获得右子树的返回节点。然后判断左右子树返回节点的情况

                1)如果左右子树返回的节点都不为空,说明此处的根节点就是最近的公共祖先(注意题中的条件 - 树中所有的元素都不相同,p不等于q,因此p和q在二叉树中是唯一的,不存在找到的两个节点都是p或者都是q的情况;而在遍历的时候采取后序遍历的方式,因此肯定是自底向上,最小的);

                2)如果左子树返回的节点都为空而右子树不为空,说明此处的右子树节点就是最近的公共祖先;

                 3)如果右子树返回的节点都为空而左子树不为空,说明此处的左子树节点就是最近的公共祖先;

代码实现:

 public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        //终止条件
        if(root == null || root == q || root == p){
            return root;
        }

        TreeNode left = lowestCommonAncestor(root.left, p, q);//左子树
        TreeNode right = lowestCommonAncestor(root.right, p, q);//右子树
        
        TreeNode result = null;
        if(left != null && right != null){
            result = root;
        }else if(left == null){
            result = right;
        }else{
            result = left;
        }

        //返回值
        return result;
    }

总结:

对于二叉树来说做题时可以考虑的点:

如果是创建一颗新的二叉树 - 考虑前序遍历;

如果需要利用二叉树的有序性 - 考虑中序遍历;

如果是需要采用自底向上获得答案 - 考虑后序遍历。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值