算法刷题 - 二叉树(层序遍历合集)

89 阅读9分钟

二叉树层序遍历 - 专题

从左到右遍历。一般借助队列结构,待输出的节点从队尾入队,队头出队。

  • 队列队首元素为下一个待出队的元素
  • 每次将队首节点出队输出当前节点(一般为 val 字段)后;
  • 查询该节点是否存在左右子树节点,如果存在则将其从队尾入队
  • 循环上述步骤;直到队为空

示例

现有一个二叉树总共有5个节点。 层序遍历思路如下:

  • 蓝色节点表示已经入队
  • 橙色表示未入队

image.png

image.png

image.png

image.png

image.png

image.png


经典例题

题目 01 - 二叉树层序遍历

原题链接

LeetCode 102 - 二叉树的层序遍历

题目描述

给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)

思路

  • 依照图示;使用队列辅助实现

代码

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> ans = new ArrayList<>();
        List<Integer> level;

        //检查输入
        if(null == root) return ans;
        
        //辅助队列; 先加入头节点
        Deque<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        
        while(!queue.isEmpty()) {
            //获取当前层拥有几个节点
            int size = queue.size();

            //记录当前层的所有元素
            level = new ArrayList<>();

            //遍历当前层内的所有结点
            for(int i = 0; i < size; i++) {
                TreeNode node = queue.pop();
                level.add(node.val);
                
                //记录下一层的左子节点
                if(node.left != null) queue.offer(node.left);
                if(node.right != null) queue.offer(node.right);
            }

            //记录当前层记录到的所有结果结点
            ans.add(level);
        }

        return ans;
    }
}

结果分析

  • 时间复杂度: O(N)
  • 空间复杂度: O(N)

题目 02 - 二叉树层序遍历Ⅱ

原题链接

LeetCode 107 - 二叉树的层序遍历Ⅱ

题目描述

给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)

思路

  • 同第一题思路一致;最后把记录结果的集合反转

代码

class Solution {

    public List<List<Integer>> levelOrderBottom(TreeNode root) {
        //记录每一层遍历的结果
        List<List<Integer>> ans = new LinkedList<>();

        //检查输入
        if(root == null) return ans;

        //层序遍历
        Deque<TreeNode> queue = new LinkedList<>();
        //先记录根结点结果
        queue.offer(root);

        while(!queue.isEmpty()) {
            int size = queue.size();
            //遍历上一层记录的所有结点
            List<Integer> level = new ArrayList<>();
            for(int i = 0; i < size; i++) {
                TreeNode tmp = queue.pop();

                //记录该层,顺序正常
                level.add(tmp.val);

                //如果左右子节点不为空,则将其加入到下一层遍历
                if(tmp.left != null) queue.add(tmp.left);
                if(tmp.right != null) queue.add(tmp.right);
            }

            //方法一: 添加到首部;使得输出结果翻转
            ans.addFirst(level);
            
            /*
             方法二: 或者基于 Collections 工具集
             ans.add(level);
            */
        }
        
        
        /*
         方法二: 或者基于 Collections 工具集
         Collections.reverse(ans);
        */

        return ans;
    }
}

结果分析

  • 时间复杂度: O(N)
  • 空间复杂度: O(N)

题目 03 - 二叉树的右视图

原题链接

LeetCode 199 - 二叉树的右视图

题目描述

给定一个二叉树的 根节点 root,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。

思路

  • 判断是否遍历到单层最后面的元素,如果是,就放进结果集中;

代码

class Solution {
    public List<Integer> rightSideView(TreeNode root) {
        List<Integer> ans = new LinkedList<>();
        if(root == null) return ans;

        //遍历每一层的结果
        Deque<TreeNode> level = new LinkedList<>();  
        //将每一层的最后一个结点加入结果集
        level.offer(root);
        while(!level.isEmpty()) {
            int size = level.size();
            for(int i = 0; i < size; i++) {
                TreeNode tmp = level.pop();
                if(i == size - 1) {
                    ans.add(tmp.val);
                }
                //如果左右结点不为空;加入结果集内
                if(tmp.left != null) level.add(tmp.left);
                if(tmp.right != null) level.add(tmp.right);
            }
        }
        return ans;
    }
}

结果分析

  • 时间复杂度: O(N)
  • 空间复杂度: O(N)

题目 04 - 求二叉树的平均值

原题链接

LeetCode 637 - 二叉树层的平均值

题目描述

给定一个非空二叉树的根节点 root , 以数组的形式返回每一层节点的平均值。与实际答案相差 10-5 以内的答案可以被接受。

思路

  • 层序遍历,每一层求总和之后与当前层内二叉树结点个数相除

代码

class Solution {
    /*
     * 层序遍历,每一层求总和之后与当前层内二叉树结点个数相除
     */
    public List<Double> averageOfLevels(TreeNode root) {
        List<Double> ans = new LinkedList<>();
        if (root == null)
            return ans;
        Deque<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        while (!queue.isEmpty()) {        // 队列不为空出队获取队首节点
            int size = queue.size();
            Double sum = 0.0;
            for (int i = 0; i < size; i++) {
                TreeNode peek = queue.poll();
                sum += peek.val;
                if (peek.left != null) {  // 左子节点不为空
                    queue.offer(peek.left);
                }
                if (peek.right != null) { // 右子节点不为空
                    queue.offer(peek.right);
                }
            }
            ans.add(sum / size);         // 计算该层的节点值平均值
        }
        return ans;
    }
}

结果分析

  • 时间复杂度: O(N)
  • 空间复杂度: O(N)

题目 05 - N 叉树的层序遍历

原题链接

LeetCode 429 - N叉树的层序遍历

题目描述

给定一个 N 叉树,返回其节点值的层序遍历 (即从左到右,逐层遍历)。 树的序列化输入是用层序遍历,每组子节点都由 null 值分隔(参见示例)。

思路

  • 多了一个子节点,但是代码主体逻辑不变
  • 由于是 N 叉树,子节点使用 List 集合存储
  • 将判断是否还有子节点的逻辑切换为判断 List 集合是否为空

代码


class Solution {
    public List<List<Integer>> levelOrder(Node root) {
        List<List<Integer>> ans = new LinkedList<>();
        if (root == null) { // 检查输入
            return ans;
        }
        Deque<Node> queue = new LinkedList<>(); // 队列,用于层序遍历
        queue.offer(root); // 根节点入队
        while (!queue.isEmpty()) {
            int size = queue.size(); // 本层节点的个数
            List<Integer> level = new LinkedList<>(); // 本层遍历的节点结果
            for (int i = 0; i < size; i++) {
                Node peek = queue.poll();
                level.add(peek.val);
                if (!peek.children.isEmpty()) {
                    // 如果当前节点的子节点不为空
                    for (int j = 0; j < peek.children.size(); j++) {
                        // 将其子孩子加入到队列
                        queue.offer(peek.children.get(j));
                    }
                }
            }
            // 将层的遍历结果加入结果集 ans
            ans.add(level);
        }
        return ans;
    }
}

结果分析

  • 时间复杂度: O(N)
  • 空间复杂度: O(N)

题目 06 - 在每个树行中找到最大值

原题链接

LeetCode 515 - 每个树行内的最大值

题目描述

给定一棵二叉树的根节点 root ,请找出该二叉树中每一层的最大值。

思路

  • 同样,借助队列对每一层完成遍历
  • 使用一个临时变量 max 记录每一层的最大值;
  • 每遍历完一层将 max 加入到结果集内

代码

class Solution {
    public List<Integer> largestValues(TreeNode root) {
        List<Integer> ans = new LinkedList<>();
        if (root == null) { // 检查输入
            return ans;
        }
        
        // 根节点入队
        Deque<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        
        // 遍历每一层获取到最大值
        while (!queue.isEmpty()) {
            // 得到这一层节点的个数
            int size = queue.size();
            int max = Integer.MIN_VALUE;       // 遍历记录当前层的最大值
            for (int i = 0; i < size; i++) {
                TreeNode peek = queue.poll();
                max = Math.max(max, peek.val); // 更新当前层最大值
                if (peek.left != null) {       // 如果左右节点不为空,将其入队
                    queue.offer(peek.left);
                }
                if (peek.right != null) {
                    queue.offer(peek.right);
                }
            }
            ans.add(max); // 将最大值记录
        }
        return ans;
    }
}

结果分析

  • 时间复杂度: O(N)
  • 空间复杂度: O(N)

题目 07 - 填充每个节点的下一个右侧节点指针

原题链接

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

题目描述

给定一个 完美二叉树 ,其所有叶子节点都在同一层,每个父节点都有两个子节点。二叉树定义如下:

struct Node {
    int val;
   Node *left;
   Node *right;
   Node *next;
}

填充它的每个 next 指针,让这个指针指向其下一个右侧节点。 如果找不到下一个右侧节点,则将 next 指针设置为 NULL。 初始状态下,所有 next 指针都被设置为 NULL。

思路

  • 同样,借助队列对每一层完成遍历
  • 每一层借助临时变量 prev 记录上一个遍历到的节点
  • 如果 prev == null 遍历到该层的首个节点,
  • prev 不为 null, 则当前遍历到的不是首个节点

代码

class Solution {
    public Node connect(Node root) {
        Deque<Node> queue = new LinkedList<>();
        // 检查输入
        if (root == null) {
            return root;
        }
        // 将节点加入到队列
        queue.offer(root);

        // 遍历每一层
        while (!queue.isEmpty()) {
            // 当前树层的节点数目
            int size = queue.size();
            Node pre = null;

            // 遍历当前层所有节点并记录其子节点
            for (int i = 0; i < size; i++) {
                Node tmp = queue.pop();

                // 如果记录前驱节点不为空,则更新 pre 的 next 域指向 tmp
                if (pre != null) {
                    pre.next = tmp;
                }

                // 更新前驱结点
                pre = tmp;

                // 如果存在左右结点,将其入队
                if (pre.left != null) {
                    queue.offer(tmp.left);
                }
                if (pre.right != null) {
                    queue.offer(tmp.right);
                }
            }
        }
        // 返回根节点
        return root;
    }
}

结果分析

  • 时间复杂度: O(N)
  • 空间复杂度: O(N)

题目 08 - 二叉树的最大深度

原题链接

LeetCode 104 - 二叉树的最大深度

题目描述

给定一个二叉树 root ,返回其最大深度。 二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。

image.png

思路

  • 借助队列,遍历每一层内的各个节点; depth 用于记录深度
  • 如果当前节点存在子节点,说明还有下一层;深度 + 1
  • 如果遍历完当前层内的各个节点均不存在子节点;即得到最大深度

代码

class Solution {
    public int maxDepth(TreeNode root) {
        int depth = 0;      // 记录最大深度
        if (root == null) { // 检查输入
            return 0;
        }
        Deque<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        while (!queue.isEmpty()) {
            int size = queue.size();
            depth++;      // 更新深度
            for (int i = 0; i < size; i++) {
                TreeNode peek = queue.pop();
                // 左子节点不为空则入队
                if (peek.left != null) {
                    queue.offer(peek.left);
                }
                // 右子节点不为空则入队
                if (peek.right != null) {
                    queue.offer(peek.right);
                }
            }
        }
        return depth;
    }
}

结果分析

  • 时间复杂度: O(N)
  • 空间复杂度: O(N)

题目 09 - 二叉树的最小深度

原题链接

LeetCode 111 - 二叉树的最小深度

题目描述

给定一个二叉树,找出其最小深度。 最小深度是从根节点到最近叶子节点的最短路径上的节点数量。 说明:叶子节点是指没有子节点的节点。

image.png

思路

  • 层序遍历,若当前树层存在节点且该节点左或者右节点存在,更新最小深度
  • 如果遍历该节点左右节点都为空,则返回最小深度

代码

class Solution {
    public int minDepth(TreeNode root) {
        if (root == null) { // 检查输入
            return 0;
        }
        Deque<TreeNode> queue = new LinkedList<>();
        int minDepth = 1; // 仅有根节点的情况;深度为 1
        queue.offer(root); // 根节点入队
        if (root.left == null && root.right == null) {
            return minDepth;
        }
        // 判断树下一层是否存在节点
        while (!queue.isEmpty()) {
            int size = queue.size();
            for (int i = 0; i < size; i++) {
                TreeNode peek = queue.pop(); // 队首节点
                if (peek.left == null && peek.right == null) {
                    return minDepth;
                }
                if (peek.left != null) {
                    queue.offer(peek.left);
                }
                if (peek.right != null) {
                    queue.offer(peek.right);
                }
            }
            minDepth++; // 更新最小深度
        }
        // 否则无最小深度
        throw new RuntimeException("Not such element!");
    }
}

结果分析

  • 时间复杂度: O(N)
  • 空间复杂度: O(N)

参考文献

代码随想录 - 二叉树层序遍历