「剑指 Offer 32-III. 从上到下打印二叉树III」

330 阅读3分钟

「剑指 Offer 32-III. 从上到下打印二叉树III」

Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情

题目描述(level 中等)

请实现一个函数按照之字形顺序打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右到左的顺序打印,第三行再按照从左到右的顺序打印,其他行以此类推。

示例
例如:
给定二叉树: [3,9,20,null,null,15,7],
    3
   / \
  9  20
    /  \
   15   7
      
返回其层次遍历结果:
[
  [3],
  [20,9],
  [15,7]
]
思路分析
  • 对返回结果区分奇偶法

是对二叉树层序遍历进一步考察,结合前面两题,分别考察了二叉树的简单按层打印,整体返回结果层序遍历,单层结果返回。而本题是对按层返回结果的同时作奇偶变换。之前分析过,二叉树层序遍历BFS广度优先搜索,一般首先能想到的是使用队列先入先出特性来实现。分析此题,与32-II的唯一区别就是对奇偶的层数的不同,奇数层从左到右打印,偶数层从右到左打印。既然结果是一个List,只需要遍历List根据下标index的奇偶性对子集合List作反转即可。但是如果真在面试过程中遇到了这题,建议还是写两种解法,依个人愚见,本题更倾向于对双端队列的考察。结果反转思路总结:

  • 二叉树为空时的处理
  • 借助队列Queue将遍历到的节点存入到队列中,首先入队列的是根节点(root)
  • 从左到右,添加子节点(如果存在)
  • 对于每一层数字加入到集合当中,采用从高到低的循环添加方式
  • 根据奇偶性对temp list进行反转

「剑指Offer 32-Ⅰ.从上到下打印二叉树」

「剑指Offer 32-II. 从上到下打印二叉树 II」

除了对结果根据奇偶性进行反转,还可以通过双端队列对临时结果list进行预先处理,思想其实是一样的,当被处理的行数为偶数时(要求从右到左打印)将数字添加到队列头部,当被处理的行数为奇数时,将数字添加到队列尾部

  • 层序遍历+双端队列处理临时list

思路总结如下:

  • 采用双端队列保存每一层的数据,偶数层将数字添加到队列头部
  • 奇数层将数字添加到队列的尾部
以示例中例子为例
    3
   / \
  9  20
    /  \
   15   7
   
List<List<Integer>> result = new ArrayList<>();
LinkedList<Integer> temp = new LinkedList<>();

当[3]被添加到result中以后,此时result的size大小为1,
下一行为偶数([9,20]),需要被添加到头部,
但是此时层序遍历的方向是不变的;依然是从920。
当9被添加到头部(addFirst),则 temp = [9],
接着遍历20,然后再添加到头部的到
temp = [20,9],对数组进行了“反转”。
打印总结果时得到的就是反转后的内容。
代码实现
  • temp list根据奇偶性对其反转
class Solution {
  if (null == root) {
    return new ArrayList<>();
  }
  Queue<TreeNode> queue = new LinkedList<>();
  List<List<Integer>> result = new ArrayList<>();
  queue.offer(root);
      
  while (!queue.isEmpty()) {
    List<Integer> temp = new ArrayList<>();
    for (int i = queue.size(); i > 0; i--) {
      TreeNode node = queue.poll();
      if (null != node) {
        temp.add(node.val);
        if (null != node.left) {
          queue.offer(node.left);
        }
        if (null != node.right) {
          queue.offer(node.right);
        }
      }
    }
    //根据奇偶性对集合进行反转
    if (result.size() % 2 == 1) {
      Collections.reverse(temp);
    }
    result.add(temp);
  }
  return result;
}

这里对临时集合temp list反转判断的是当前总集合list的大小的奇偶性,如果当前大小size偶数那么待添加的子List则不需要反转(从右到左),反之亦然。

  • 双端队列对每层结果进行预处理
class Solution {
  if (null == root) {
    return new ArrayList<>();
  }
  Queue<TreeNode> queue = new LinkedList<>();
  List<List<Integer>> result = new ArrayList<>();
  queue.offer(root);
     
  while (!queue.isEmpty()) {
    LinkedList<Integer> temp = new LinkedList<>();
    for (int i = queue.size(); i > 0; i--) {
      TreeNode node = queue.poll();
      if (null != node) {
        /*偶数层加入队列的头部*/
        if (result.size() % 2 == 0) {
          temp.addLast(node.val);
        } else {
          temp.addFirst(node.val);
        }
        if (null != node.left) {
          queue.offer(node.left);
        }
        if (null != node.right) {
          queue.offer(node.right);
        }
      }
    }
    result.add(temp);
  }
  return result;
}
  • Tips
if (result.size() % 2 == 0) 
判断的是当前总结果result的大小size,
如果当前size为偶数那么待添加的下一层为奇数,反之亦然。
复杂度

时间复杂度O(N):层序遍历需要遍历每一个节点,与节点的个数N成线性

空间复杂度O(N):额外使用队列,空间复杂度与节点个数相关

链接

剑指 Offer 32-III. 从上到下打印二叉树III