代码随想录算法训练营 day21 || 530.二叉搜索树的最小绝对值之差,501.二叉树中的众数,236.二叉树的最近公共祖先

视频讲解:

二叉搜索树中,需要掌握如何双指针遍历!| LeetCode:530.二叉搜索树的最小绝对差_哔哩哔哩_bilibili

不仅双指针,还有代码技巧可以惊艳到你! | LeetCode:501.二叉搜索树中的众数_哔哩哔哩_bilibili

自底向上查找,有点难度! | LeetCode:236. 二叉树的最近公共祖先_哔哩哔哩_bilibili

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

思路:这道题在第一遍做的时候没有意识到时任意两个节点之间的最小差值,误认为的是父节点与子节点之间的最小差值。因此最直观的解题方法就是将树全部遍历出来,然后计算两两之间的最小差值。结合本体题目中给出的二叉搜索树,那么按中序遍历的结果是一个有序的数组,进行两两的比较就可以。而两两比较也可以还原到树的遍历过程,可以通过保存上一次访问的节点pre与当前访问的节点cur,两两之间的差值储存后使用min函数不断更新,也可以实现完全一样的效果。代码随想录

        但是,我想强调一点,这也是 代码随想录 给出的直接在树遍历时直接cur与pre进行比较,数组只需两两比较的关键问题,这一部分没有讲明白。题目中明确的是任意节点的差值,那么我们形成数组也好,在遍历树时候也好,应该是每一个节点需要与全局的其他节点之间都进行作差,然后统计最小的值保存,然后再在后续其他节点的最小值上筛选出来,总共需要做 (n-1)n/2 这么多次比较。但是现在仅仅需要两两进行比较,这是为什么?是因为二叉搜索树,是这个条件赋予了之后,我们仅仅需要定位根节点与左子树最右节点和根节点与右子树最左节点,这两种情况之中一定是最小差值的存在之处,然后对每一个具有子树的节点都这么考虑一遍,可以统计处全局最小差值,而我们还原来看,左子树的最右节点与右子树的最左节点之间在遍历后的结果上就是相邻的(中序遍历的结果),下面给出图示让大家理解。

        而二叉搜索树总是左子树所有节点小于根节点,右子树所有节点大于根节点,左子树最右这个节点是左子树中最接近根节点的,反之右子树最左也是一样,227与600是相距236最近。如果树再大一些,那么104也有两个相近的,701也同样是如此。所以除了叶子节点,每一个有子树的节点都是有两个与自己最为相接近的节点,而我们计算节点差的最小值,这表明树上一定不只是存在叶子节点,一定是有根节点或者父节点;而叶子节点起到的作用就是与其他的节点的值进行比较,两个相邻的叶子节点之间已经差了很多个中间的父节点,彼此之间的差值肯定会比根节点与其左子树最右、右子树最左来的大,从图上看距离也远的多。所以我们可以总结出这样的结论:

  • 二叉搜索树节点间的最小差值一定是中序遍历结果上靠的近的节点;即节点在中序遍历的结果上距离越近,他们的值之间的距离就变得越小。显然叶子节点与叶子节点的差值肯定比父节点与父节点之间的差值来的大;
  • 基于上一点,所以我们最小差值就是在父节点与父节点之间找;
  • 每个父节点包括根节点,与其左子树最右、右子树最左是相距最近;
  • 所以基于以上三点,我们仅仅需要统计所有父节点与其左子树最右和右子树最左之间的差值,然后再筛选中其中最小的;
  • 针对第四点其映射在实际的计算中就是中序遍历结果上两个相邻元素作差,树遍历时当前节点与上一次访问节点作差。 

        这样解析完二叉搜索树所给出的优势,才是可以正确理解并解决本题的关键。两种解法都进行了实现,递归的时间效率更快。

/**
 * 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;
 *     }
 * }
 */

// 时间复杂度o(n)
// 空间复杂度O(n) 和 O(1)(递归)
class Solution {

    public TreeNode pre = null;
    public int min = Integer.MAX_VALUE;
    public int getMinimumDifference(TreeNode root) {
    //     List<Integer> list = new ArrayList<>();
    //     inOrderTraversal(root, list);
    //     int min = Integer.MAX_VALUE;
    //     for(int i=0; i<list.size()-1; i++){
    //         int num = Math.abs(list.get(i)-list.get(i+1));
    //         min = num<min? num:min;
    //     }

    //     return min;
    // }

    // public void inOrderTraversal(TreeNode t, List<Integer> list){
    //     if(t == null)
    //         return;
    //     inOrderTraversal(t.left, list);
    //     list.add(t.val);
    //     inOrderTraversal(t.right, list);
    // }

        inOrderTraversal(root);
        return min;
    }
    public void inOrderTraversal(TreeNode t){
        if(t == null)
            return;
        // 左
        inOrderTraversal(t.left);
        // 中 且仅仅是去到最下层最左边一个节点pre为null,后续节点遍历时,pre都可以指向上一次访问问题
        if(pre != null){
            int num = Math.abs(t.val - pre.val);
            min = num<min? num:min;
        }
        pre = t;
        // 右
        inOrderTraversal(t.right);

        return;


    }


}

501. 二叉树中的众数

思路:众数是统计树上所有的节点各个节点出现的次数,然后输出次数相同的一个或多个数为二叉树的众数。本题中仍然是二叉搜索树BST,所以中序遍历的序列仍然为有序的序列,因此相同的元素势必出现在一起,所以只要统计每一个相邻出现的元素中各有几个是一样的,然后输出其中出现次数最多的即可。

其中注意一个小知识点,List中每一个Integer类型的元素都是一个Integer对象,如果i与j位置的元素值都在-128~127之间,则两个对象对应是内存中同一个地址;如果值不相同那么肯定对象也不同;如果i与j位置的元素相同,但是不在[-128,127]区间内,那么各自Integer的地址也不同。因此list.get(i) == list.get(i+1) 这种操作需要慎用,最好是用list.get(i) - list.get(i+1) == 0 或者list.get(i).equals(list.get(i+1))这样的操作;

/**
 * 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;
 *     }
 * }
 */
// 时间复杂度O(n),空间复杂度O(n)
class Solution {
    public int[] findMode(TreeNode root) {
        // 结合二叉搜索树的特点,与节点值相邻的节点在中序遍历结果上一定都是靠在一起的
        List<Integer> list = new ArrayList<>();
        // 获取树的所有节点序列
        inOrderTraversal(root, list);
        
        int max = 1;
        int count = 1;
        // 结果列表
        List<Integer> ans = new ArrayList<>();

        for(int i=0; i<list.size(); i++){
        
            if(i <= list.size()-2){        
                if(list.get(i) - list.get(i+1) == 0){
                    count++; 
                }   
                else{
                    if(count > max){
                        max = count;
                        ans.clear();
                        ans.add(list.get(i));
                    }
                    else if(count == max)
                        ans.add(list.get(i));
                    count = 1;
                }
            }
            else if(i == list.size()-1){
                if(count > max){
                    max = count;
                    ans.clear();
                    ans.add(list.get(i));  
                }
                else if(count == max)
                    ans.add(list.get(i));
            }
        }

        int[] res = new int[ans.size()];
        for(int i=0; i<ans.size(); i++)
            res[i] = ans.get(i);
        return res;
    }

    public void inOrderTraversal(TreeNode t, List<Integer> list){
        if(t == null)
            return;
        inOrderTraversal(t.left, list);
        list.add(t.val);
        inOrderTraversal(t.right, list);
        return;
    }
}

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

思路:直观的思路就是求两条路径的交点,非常类似于349.两个链表的交集 这题的解题思路,我使用两个栈来保存各自的路径,然后从头开始遍历,出现第一个不同的元素时则停止,两者的最近公共祖先位于第一个不同元素索引的前一个位置;或者有一边已经走到了结尾,那么最近祖先就是走到末尾的指针所指向的元素。

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
// 时间复杂度O(2n) ~ O(3n)
// 空间复杂度O(2m),m为p,q对应路径的长度

class Solution {
    boolean flag = false;
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        // 直观的理解,两个节点的公共祖先,就是两个节点所在路径上存在交点,那么很明显就是找到两个节点所在序列
        List<TreeNode> queue_p = new ArrayList<>();
        List<TreeNode> queue_q = new ArrayList<>();

        this.flag = false;
        DFS(root, queue_p, p);
        this.flag = false;
        DFS(root, queue_q, q);

        int len = queue_p.size()<queue_q.size()?queue_p.size():queue_q.size();
        int index = 0;
        for(int i=0; i<len; i++){
            if(queue_p.get(i) != queue_q.get(i)){
                index = i-1;
                break;
            }
            if(i == len-1){
                index = len-1;
            }
        }
            
        return queue_p.get(index);
    }

    public void DFS(TreeNode t, List<TreeNode> queue, TreeNode tar){
        if(t == null || flag == true)
            return;
        // 路径遍历使用先序
        queue.add(t);
        if(t == tar){
            flag = true;
            return;
        }
        // 当找到路径之后,不能再加入节点
        if(!flag)
            DFS(t.left, queue, tar);
        if(!flag)
            DFS(t.right, queue, tar);
        // 不能再删去节点
        if(!flag)
            queue.remove(queue.size()-1);
    }
}

  • 23
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值