【Java实现】剑指Offer32.3——从上到下打印二叉树(三):之字形打印二叉树

在这里插入图片描述
同学们,我把《剑指Offer》中所有“树”标签的习题及思路整理出来了,对照学习效果更佳:


建议参考以下两道题,循序渐进:
剑指Offer32.1——从上到下打印二叉树
剑指Offer32.2——从上到下打印二叉树(二)

这道题我自己写了好久,每次都是卡在某个测试用例,非常崩溃,最后看了大神的解法,觉得豁然开朗,自己的问题在于:没有控制好奇偶层和出队列顺序的关系,导致十分混乱

这么说大家可能难以理解,回到这道题,我们先想一下逆序打印,共有几种方法?
前提:返回值要求是List<List<Integer>>,因此,每一层都在一个小链表中。

  1. 第一种:每一层获取到的数字,加入小链表的顺序不同:奇数层addLast,偶数层addFirst(加入头部即实现逆序)。说白了,就是正常加入,出来的时候逆序
  2. 第二种:不用变量去控制奇偶,因为第一次进入循环一定是奇数层,所以直接逻辑区分奇偶。正常出,加入的时候逆序
  3. 第三种,每一层获取的数字,正常加入List,在加入大List之前,判断奇偶,看是否反转

上面三种想法,也就是这道题的三种解法,总的来说有以下关键点:

  • 首先,层序遍历一定要使用队列
  • 第二,要想逆序打印,就需要双端队列
  • 第三,要想到集合的reverse方法

方法一:加入小集合的时候逆序

跟正常的层序遍历一样,同样需要借助队列的帮助,去循环遍历队列。
这种做法需要注意的是,每一层的遍历结果,同样需要加入到一个双端队列,在加入的时候实现逆序的效果
这样做的好处:相当于解耦操作,不需要双端队列来控制顺序了

代码:

class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        if(root == null) return new ArrayList<>();

        Queue<TreeNode> queue=new LinkedList<>();
        queue.add(root);

        List<List<Integer>> res=new ArrayList<>();
        LinkedList<Integer> list=null;
        
        while(!queue.isEmpty()) {
            list=new LinkedList<>();
            for(int i=queue.size();i>0;i--) {
                TreeNode node=queue.poll();
                if(res.size()%2==0) {
                    list.addLast(node.val);
                } else {
                    list.addFirst(node.val); 
                }
                if(node.left!= null) queue.add(node.left);
                if(node.right!= null) queue.add(node.right);  
            }
            res.add(list);
        }
        return res;
    }
}

方法二:无需判断奇偶层,因为刚进去就是奇数层

简单来讲,这种方法主要包含两步!

  1. 把当前行的节点打印出来!(出队列)
  2. 把子节点加入队列

难点在于:
需要找到加入和打印的关系,因为上一行加入的下一行就要打印出来,而且是逆序

而诀窍在于:

奇数层从左到右出队列,子节点(先左后右)从左到右加入(addLast)
偶数层从右到左出队列,子节点(先右后左)从右到左加入(addFirst)

这种方法不需要控制变量来控制奇偶,第一次进入循环,就是奇数层,此后通过不断的更新队列中的节点,来继续循环
而每次循环就只包含奇偶两次操作。

注意:这种方法需要双端队列
之前我就错在没有把下层节点加入到队尾(或队首),导致下层节点的值提前输出

代码:

class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        if(root == null) return new ArrayList<>();

        Deque<TreeNode> deque=new LinkedList<>();
        deque.addLast(root);

        List<List<Integer>> res=new ArrayList<>();
        List<Integer> list=null;

        while(!deque.isEmpty()) {
            list=new ArrayList<>();
            for(int i=deque.size();i>0;i--) {
                TreeNode node=deque.removeFirst();
                list.add(node.val);
                if(node.left != null) deque.addLast(node.left);
                if(node.right != null) deque.addLast(node.right);
            }
            res.add(list);
            if(deque.isEmpty()) break;
            list=new ArrayList<>();
            for(int i=deque.size();i>0;i--) {
                TreeNode node=deque.removeLast();
                list.add(node.val);
                if(node.right != null) deque.addFirst(node.right);
                if(node.left != null) deque.addFirst(node.left);
            }
            res.add(list);
        }
        return res;
    }
}

方法三:正常运行,小List加入前判断正反

第三种方法前面只需要正常运行即可,正常出队列,加入队列,只需要在每一层结束前判断,是否需要将List转过来即可

代码:

class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        if(root == null) return new ArrayList<>();

        Queue<TreeNode> queue=new LinkedList<>();
        queue.add(root);

        List<List<Integer>> res=new ArrayList<>();
        List<Integer> list=null;

        while(!queue.isEmpty()) {
            list=new ArrayList<>();
            for(int i=queue.size();i>0;i--) {
                TreeNode node=queue.poll();
                list.add(node.val);
                if(node.left != null) queue.add(node.left);
                if(node.right != null) queue.add(node.right);
            }
            if(res.size()%2!=0) Collections.reverse(list);
            res.add(list);
        }
        return res;
    }
}

复杂度分析:

  • 时间:O(N),因为都需要遍历每一个节点
  • 空间:O(N),因为需要额外队列空间
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值