层序遍历简介
广度优先在面试里出现的频率非常高,整体属于简单题,但是很多人面试遇到时就直接放弃了,实在可惜。我们本章就集中研究一下到底难在哪里。
广度优先又叫层次遍历,基本过程如下:
该过程不复杂,如果能将树的每层次分开了,是否可以整点新花样?首先,能否将每层的元素顺序给反转一下呢?能否奇数行不变,只将偶数行反转呢?能否将输出层次从低到root逐层输出呢?再来,既然能拿到每一层的元素了,能否找到当前层最大的元素?最小的元素?最右的元素(右视图)?最左的元素(左视图)?整个层的平均值?
很明显都可以!这么折腾有啥用呢?没啥用!但这就是层次遍历的高频算法题!这就是LeetCode里经典的层次遍历题!
102.二叉树的层序遍历
107.二叉树的层次遍历II
199.二叉树的右视图
637.二叉树的层平均值
429.N叉树的前序遍历
515.在每个树行中找最大值
116.填充每个节点的下一个右侧节点指针
117.填充每个节点的下一个右侧节点指针II
103 锯齿层序遍历
除此之外,在深度优先的题目里,有些仍然会考虑层次遍历的实现方法。
基本的层序遍历与变换
二叉树的层序遍历
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
if (root == null) {
return new ArrayList<List<Integer>>();
}
List<List<Integer>> res = new ArrayList<>();
LinkedList<TreeNode> queue = new LinkedList<>();
// 将节点放入队列中,不断遍历队列
queue.add(root);
while (queue.size() > 0) {
// 获取当前队列的长度,也就是当前这一场元素个数
int size = queue.size();
ArrayList<Integer> tmp = new ArrayList<>();
// 将队列中的元素拿出来放到临时list中
// 如果节点的左右子树不为空,也放入到队列中
for (int i=0; i < size; i++) {
TreeNode t = queue.remove();
tmp.add(t.val);
if (t.left != null) {
queue.add(t.left);
}
if (t.right != null) {
queue.add(t.right);
}
}
// 如果此时的tmp就是当前层的全部元素,用 List 类型的tmp保存,加入最后结果中
res.add(tmp);
}
return res;
}
}
自底向上的层序遍历
使用链表,将每层的节点值插入到链表头部就行
public List<List<Integer>> levelOrderBottom(TreeNode root) {
List<List<Integer>> levelOrder = new LinkedList<List<Integer>>();
if (root == null) {
return levelOrder;
}
Queue<TreeNode> queue = new LinkedList<TreeNode>();
queue.offer(root);
while (!queue.isEmpty()) {
List<Integer> level = new ArrayList<Integer>();
int size = queue.size();
for (int i = 0; i < size; i++) {
TreeNode node = queue.poll();
level.add(node.val);
TreeNode left = node.left, right = node.right;
if (left != null) {
queue.offer(left);
}
if (right != null) {
queue.offer(right);
}
}
levelOrder.add(0, level);//栈
}
return levelOrder;
}
锯齿型层序遍历
采用双端队列存储每一层的值,然后用一个变量控制,是从左往右输出,还是从右往左输出
public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
List<List<Integer>> ans = new LinkedList<List<Integer>>();
if (root == null) {
return ans;
}
Queue<TreeNode> queue = new LinkedList<TreeNode>();
queue.offer(root);
boolean isOrderLeft = true;
while (!queue.isEmpty()) {
Deque<Integer> levelList = new LinkedList<Integer>();
int size = queue.size();
for (int i = 0; i < size; ++i) {
TreeNode curNode = queue.poll();
if (isOrderLeft) {
levelList.offerLast(curNode.val);
} else {
levelList.offerFirst(curNode.val);
}
if (curNode.left != null) {
queue.offer(curNode.left);
}
if (curNode.right != null) {
queue.offer(curNode.right);
}
}
ans.add(new LinkedList<Integer>(levelList));
isOrderLeft = !isOrderLeft;
}
return ans;
}
N叉树的层序遍历
借助队列实现
public class nTraversal {
public List<List<Integer>> nLevelOrder(Node root) {
List<List<Integer>> res = new ArrayList<>();
Deque<Node> q = new ArrayDeque<>();
if (root != null) {
q.addLast(root);
}
while (!q.isEmpty()) {
Deque<Node> next = new ArrayDeque<>();
List<Integer> level = new ArrayList<>();
while (!q.isEmpty()) {
Node cur = q.pollFirst();
level.add(cur.val);
for (Node child : cur.children) {
if (child != null) {
next.add(child);
}
}
}
q = next;
res.add(level);
}
return res;
}
}
class Node{
public int val;
public List<Node> children;
}
处理每层元素
在每个树行中找最大值
public List<Integer> largestValues(TreeNode root) {
List<Integer> res = new ArrayList<>();
Deque<TreeNode> deque = new ArrayDeque<>();
if (root != null) {
deque.addLast(root);
}
while (!deque.isEmpty()) {
int size = deque.size();
int levelMaxNum = Integer.MIN_VALUE;
for (int i=0; i<size; i++) {
TreeNode node = deque.poll();
levelMaxNum = Math.max(node.val, levelMaxNum);
if (node.left != null) {
deque.addLast(node.left);
}
if (node.right != null) {
deque.addLast(node.right);
}
}
res.add(levelMaxNum);
}
return res;
}
在每个树行中寻找平均值
和上面那个一样,不过要把每层的值保存然后求平均值
public List<Double> averageOfLevels (TreeNode root) {
List<Double> res = new ArrayList<>();
if (root == null) return res;
Queue<TreeNode> list = new LinkedList<>();
list.add(root);
while (list.size() != 0){
int len = list.size();
double sum = 0;
for (int i = 0; i < len; i++){
TreeNode node = list.poll();
sum += node.val;
if (node.left != null) list.add(node.left);
if (node.right != null) list.add(node.right);
}
res.add(sum/len);
}
return res;
}
二叉树的右视图
class Solution {
public List<Integer> rightSideView(TreeNode root) {
List<Integer> res = new ArrayList<>();
if (root == null) {
return res;
}
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
int size = queue.size();
for (int i=0; i < size; i++) {
TreeNode node = queue.poll();
if (node.left != null) {
queue.offer(node.left);
}
if (node.right != null) {
queue.offer(node.right);
}
if (i == size - 1) {
// 将当前层最后一个节点的值放入结果列表
res.add(node.val);
}
}
}
return res;
}
}
最底层最左边
我们可以发现,正常执行层次遍历,不管最底层有几个元素,最后一个输出的一定是是最底层最右的元素7,那这里我们就想了,能否将该处理与上一次题的翻转结合一下,每一层都是先反转再放入队列,就可以让最后一个输出的是最左的呢?
public int findBottomLeftValue(TreeNode root) {
if (root.left == null && root.right == null) {
return root.val;
}
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
TreeNode temp = new TreeNode(-100);
while (!queue.isEmpty()) {
temp = queue.poll();
if (temp.right != null) {
// 先把右节点加入 queue
queue.offer(temp.right);
}
if (temp.left != null) {
// 再把左节点加入 queue
queue.offer(temp.left);
}
}
return temp.val;
}