编程导航算法通关村第六关 | 二叉树的层序遍历

67 阅读4分钟

层序遍历简介

广度优先在面试里出现的频率非常高,整体属于简单题,但是很多人面试遇到时就直接放弃了,实在可惜。我们本章就集中研究一下到底难在哪里。

广度优先又叫层次遍历,基本过程如下:

该过程不复杂,如果能将树的每层次分开了,是否可以整点新花样?首先,能否将每层的元素顺序给反转一下呢?能否奇数行不变,只将偶数行反转呢?能否将输出层次从低到root逐层输出呢?再来,既然能拿到每一层的元素了,能否找到当前层最大的元素?最小的元素?最右的元素(右视图)?最左的元素(左视图)?整个层的平均值?

很明显都可以!这么折腾有啥用呢?没啥用!但这就是层次遍历的高频算法题!这就是LeetCode里经典的层次遍历题!

102.二叉树的层序遍历

107.二叉树的层次遍历II

199.二叉树的右视图

637.二叉树的层平均值

429.N叉树的前序遍历

515.在每个树行中找最大值

116.填充每个节点的下一个右侧节点指针

117.填充每个节点的下一个右侧节点指针II

103 锯齿层序遍历

除此之外,在深度优先的题目里,有些仍然会考虑层次遍历的实现方法。

基本的层序遍历与变换

二叉树的层序遍历

. - 力扣(LeetCode)

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

自底向上的层序遍历

. - 力扣(LeetCode)

使用链表,将每层的节点值插入到链表头部就行

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

锯齿型层序遍历

. - 力扣(LeetCode)

采用双端队列存储每一层的值,然后用一个变量控制,是从左往右输出,还是从右往左输出

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叉树的层序遍历

. - 力扣(LeetCode)

借助队列实现

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

处理每层元素

在每个树行中找最大值

. - 力扣(LeetCode)

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

在每个树行中寻找平均值

. - 力扣(LeetCode)

和上面那个一样,不过要把每层的值保存然后求平均值

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

二叉树的右视图

. - 力扣(LeetCode)

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

最底层最左边

. - 力扣(LeetCode)

我们可以发现,正常执行层次遍历,不管最底层有几个元素,最后一个输出的一定是是最底层最右的元素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;
}