代码随想路day18|二叉树中的众数,二叉搜索树的最小绝对差,二叉树的最近公共祖先,morris遍历

文章:代码随想录

morris遍历:

其实理清楚morris遍历的过程就很好理解morris遍历了,

morris分为三个步骤:

对于每个节点,若无左树,则cur右移。

若有左树,则找到这个节点的前驱节点。就是左子节点的最右的一个节点。这个mostRight指针要遍历到这个前驱节点两次,第一次先把这个前驱节点的右指针从null指向cur,然后cur左移,继续重复遍历.第二次遍历到这个前驱节点,证明cur已经往上层返回了,把这个右指针再改回指向null.最后cur再右移动。

中序遍历代码:

    //中序 左中右
    //想想摩尔斯序是先把左子树都遍历了,然后会回到有左子树的中节点,这样会导致有左子树的节点会遍历两次.
    //那么当cur需要往右移动的时候,打印是不是就是中序了呢?
    //因为cur需要往右移动的情况有两种:
    // 1.节点无左子树,那这时候没有左子树,左序为空,cur需要右移动,移动之前打印,是不是记录的就是中呢?
    // 2.节点从左子树返回中节点。在遍历到左子树的叶子节点时,这个时候cur往右移动其实就是返回上一层的中节点,所以这个时候打印,打印的就是左序。
    public void morrisIn(TreeNode root){
        TreeNode cur=root;
        TreeNode mostRight=null;
        while(cur!=null){
            mostRight=cur.left;
            //有左子树
            if(mostRight!=null){
                //找到前驱节点
                while(mostRight.right!=null && mostRight.right!=cur){
                    mostRight=mostRight.right;
                }
                //第一次遍历到前驱节点
                if(mostRight.right==null){
                    //将右指针指向cur
                    mostRight.right=cur;
                    cur=cur.left;
                    continue;
                }else {
                    //第二次遍历到,这时候要把指针改回来了。
                    mostRight.right=null;
                }

            }
            //右移之前打印
            System.out.println(cur.val);
            //无左子树,右移
            cur=cur.right;
        }
    }

二叉树中的众数,二叉搜索树的最小绝对差:

这两道题,都是利用搜索树的特性。中序遍历一定是有序数组,通过双指针的方式中序遍历树并更新变量或者集合的值,最后返回变量或者集合。知道这个特性,思路就很容易,基本都是两两比较。

这里我做的时候还没学morris,其实都可以用morris来做,先附上递归代码,后序二刷尝试用morris来解决.

二叉搜索树的最小绝对差代码:

 //双指针遍历二叉搜索树
    //中序遍历,因为已经时二叉搜索树了,所以中序遍历一定是升序无重复的,所以只要前后两两比较就能找到最小值.

    TreeNode pre=null;
    int min=Integer.MAX_VALUE;
    public int getMinimumDifference(TreeNode root) {
        if (root==null) return 0;
        traversalIn(root);
        return min;
    }

    private void traversalIn(TreeNode root) {
        if(root==null) return;
        traversalIn(root.left);
        if(pre!=null){
            //因为中序是升序的,所以不需要用绝对值方法
            //给最小值每次重新赋值
            min=Math.min(min,root.val- pre.val);
        }
        pre=root;
        traversalIn(root.right);
    }

二叉树中的众数代码:

 public static int[] findMode(TreeNode root) {
        if (root == null) return null;
        traversalIn(root);
        int[] result = new int[record.size()];
        for (int i = 0; i < record.size(); i++) {
            result[i] = record.get(i);
        }
        return result;
    }
    //这里为什么添加的是root.val而不是pre.val呢?
    //其实添加pre.val的思路更好理解,count完之后再进行比较.
    //但是尝试之后你会发现,写pre.val的话,开头和结束的临界条件判断很麻烦,不仅很多地方逻辑相同,冗余,还要加上特殊的判断条件,速度反而会更慢.
    //比如pre为空的时候,就是第一个节点的时候,你需要直接加入这个元素,不然当树节点只有一个的时候会返回一个空结果集。
    //比如null为空的时候,需要再判断一次count和maxCount,做添加或者清空操作,这里逻辑和后面的代码冗余,
    // 然后同时root为空的时候并不代表遍历完全,如果父节点的右或者左还有和pre相同的连续节点,这样后面冗余的代码还会执行多次元素就会被重复添加,
    // 那么就还得在这两处添加时加入contains判断,所以代码就很冗余复杂,速度会更慢.具体自实现代码在leeCode上提交了.
    //所以还是比较cur指针要快很多,总结一下这种需要连续统计结果的,不是两两比较的,都统计cur会更好实现.
    private static void traversalIn(TreeNode root) {
        if (root == null) {
            return;
        }
        traversalIn(root.left);

        int rootValue = root.val;
        // 计数
        if (pre == null || rootValue != pre.val) {
            count = 1;
        } else {
            count++;
        }
        // 更新结果以及maxCount
        if (count > maxCount) {
            record.clear();
            record.add(rootValue);
            maxCount = count;
        } else if (count == maxCount) {
            record.add(rootValue);
        }
        pre = root;

        traversalIn(root.right);

    }


//统一迭代法 栈
    public int[] findModeStack(TreeNode root) {
        int count = 0;
        int maxCount = 0;
        TreeNode pre = null;
        LinkedList<Integer> res = new LinkedList<>();
        Stack<TreeNode> stack = new Stack<>();

        if(root != null)
            stack.add(root);

        while(!stack.isEmpty()){
            TreeNode curr = stack.peek();
            if(curr != null){
                stack.pop();
                if(curr.right != null)
                    stack.add(curr.right);
                stack.add(curr);
                stack.add(null);
                if(curr.left != null)
                    stack.add(curr.left);
            }else{
                stack.pop();
                TreeNode temp = stack.pop();
                if(pre == null)
                    count = 1;
                else if(pre != null && pre.val == temp.val)
                    count++;
                else
                    count = 1;
                pre = temp;
                if(count == maxCount)
                    res.add(temp.val);
                if(count > maxCount){
                    maxCount = count;
                    res.clear();
                    res.add(temp.val);
                }
            }
        }
        int[] result = new int[res.size()];
        int i = 0;
        for (int x : res){
            result[i] = x;
            i++;
        }
        return result;
    }

二叉树的最近公共祖先

思路:

其实逻辑了解一次之后就比较简单。

 //有两种情况:
    //第一种情况两个节点在同一层同一个节点下,那么当前中节点就是最近公共祖先.
    //第二种情况是两个节点不在同一层,但在同一边的子树,那么最先遍历到的目标节点本身就是最近公共祖先.
    //你可能会有疑问?那如果不在同一层呢也不在同一边子树呢?那是不是根节点就是最近公共祖先?
    //那如果在同一层,但是不在同一节点下呢?那是不是相对上面某一个父节点来说,这两个节点分别在它的左右子树?那这个节点是不是就是最近的公共祖先?
    //其实你列举下来就会发现,其实过程就是找到目标结点之后,往上返回路径,找到两条路径的相遇节点。这样递归就很好理解了。


    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if(root==null) return null;
        //如果是目标节点,则返回这个目标节点
        if(root==p || root==q)return root;

        TreeNode left=lowestCommonAncestor(root.left,p,q);
        TreeNode right=lowestCommonAncestor(root.right,p,q);

        //如果两个目标节点的返回路径在当前节点相遇,那么当前节点就是最近公告祖先
        if(left!=null && right!=null) return root;
        else if(left==null && right!=null) return right;
        else if(left!=null && right==null) return left;
        //如果两边都是空,则都没找到则继续返回空
        else  return null;

    }

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值