20分钟速刷十道二叉树层序遍历题

目录

0. 前言

1. 广度优先搜索

1.1 二叉树示例

1.2 无向图示例

1.3 岛屿题示例

1.4 关键思路 

第1题:102. 二叉树的层序遍历

1. 题目介绍

2. 思路

3. 代码

Java:

C++:

JS:

第2题:107. 二叉树的层序遍历 II

1. 题目介绍

2.思路

第3题:199. 二叉树的右视图

1. 题目介绍

2. 思路: 

3. 代码

第4题:637. 二叉树的层平均值

1. 题目介绍

2. 思路

3. 代码

第5题:429. N 叉树的层序遍历

1. 题目介绍

2. 思路

3. 代码

第6题:515. 在每个树行中找最大值

1. 题目介绍

2. 思路

3. 代码

第7题:116. 填充每个节点的下一个右侧节点指针

1. 题目介绍

2. 思路

3. 代码

第8题:117. 填充每个节点的下一个右侧节点指针

第9题:104. 二叉树的最大深度

1. 题目介绍

2. 思路

第10题:111. 二叉树的最小深度

1. 题目介绍

2. 思路

3. 代码


0. 前言

本文列出10道经典的树层序遍历题,

这些题都是非常简单的题,做完可以对层序遍历流程非常熟悉,同时对广度优先搜索有初步理解:

话不多说,上菜单!

102. 二叉树的层序遍历 - 力扣(LeetCode)

107. 二叉树的层序遍历 II - 力扣(LeetCode)

199. 二叉树的右视图 - 力扣(LeetCode)

637. 二叉树的层平均值 - 力扣(LeetCode)

429. N 叉树的层序遍历 - 力扣(LeetCode)

515. 在每个树行中找最大值 - 力扣(LeetCode)

116. 填充每个节点的下一个右侧节点指针 - 力扣(LeetCode)

117. 填充每个节点的下一个右侧节点指针 II - 力扣(LeetCode)

104. 二叉树的最大深度 - 力扣(LeetCode)

111. 二叉树的最小深度 - 力扣(LeetCode)

1. 广度优先搜索

层序遍历是个经典的广度优先搜索题 ,一层一层地拨开你的心。

给出几个经典示例

1.1 二叉树示例

无疑就是本文题目示例:从根节点开始,到最下面一层的叶子节点,一层一层地遍历。

1.2 无向图示例

关于无向图,如果是从A节点出发,与他直接关联的3个节点B、C、D作为它的广度优先搜索的第一层。

1.3 岛屿题示例

图例来自代码随想录,展示了9宫格的“岛屿”排布。

对于中心节点,它的广度优先则是遍历它的上下左右四个方向,这是它的第一层,能够一步就到达下一步,不过也有少数情况周围8个角都算第一层,特别情况特别处理。

1.4 关键思路 

对于广度优先搜索题型,看到广度两个字就要想起一种数据结构:队列!

  1. 先将根节点,即初始节点,第一个要访问的节点,率先放入队列。
  2. 然后开始循环,判断队列是否为空,不为空就循环。
  3. 先提前得到此时队列的size()。
  4. 遍历size()次:
    1. 先出队,对出队后的节点根据需求进行处理
    2. 对这个出队节点的相邻的一层节点进行判断,对于树的话,即是判断它的孩子是否为空,二叉树就判断左右两个孩子,N叉树就判断所有孩子列表,孩子节点不为空,则加入队列。
  5. 此轮循环遍历size()次后可以对这一层的元素做一定的处理,比如更新这一层的最大或最小值节点,计算平均值等等。
  6. 当队列中再也没有任何节点,返回。

上菜!直接从做题中理解思路~

第1题:102. 二叉树的层序遍历

1. 题目介绍

2. 思路

  • 树判空,为空直接返回结果列表。
  • 定义队列, 并将根节点3放入队列。

  • 开始循环,条件为队列不为空。
  • 提前获取队列中节点个数, 第一次的话, size()就是1, 只有3一个元素。
  • 每一层都定义一个一维的临时列表,用于存储这一层的节点值。
  • 取出队头元素, 对其进行处理。这道题是层序遍历所有节点, 则是添加值进临时列表, 其他题就要看情况灵魂变动。如何处理是层序遍历各种题型的不同之处, 这一步请注意。

这时候可以想下为什么要提前获取队列中节点个数:如果for(int i = 0; i < queue.size(); i++) 这样写会发生什么:

假如队列中有8个节点, 照理说会遍历8次, 此时:循环条件是for(;0 < 8;)

此时取出来一个节点, 队列大小实时更新为7, 因为i++, 到了下一轮:for(;1<7;)

接着又到了下一轮:for(;2<6;)、for(;3<5;)、for(;4<4;), 这不是只能遍历4次了嘛。

所以提前获取队列节点个数的目的就是锁定for循环的次数。

  • 判断节点的左右孩子, 不为空就按照顺序放进队列。
  • 这一轮循环完, 将已经获取了一层节点值的临时列表添加进去结果列表。

第一次做, 就走完吧。

接下来又是循环, 将9取出来, 加入临时列表, 判断节点9孩子是否为空, 都为空则不管:

 

接下来将节点20取出来, 加入临时列表, 判断节点20孩子不为空,加入队列。此时这一轮又遍历完,将临时列表加入到结果列表:

接下来将节点15取出来, 加入临时列表, 判断节点15孩子为空。

接下来将节点7取出来, 加入临时列表, 判断节点7孩子为空。此时这一轮又遍历完,将临时列表加入到结果列表:

 最后while(!queue.isEmpty())条件为false, 闹剧结束了, return res; 轻松通关~

3. 代码

Java:

    public static List<List<Integer>> levelOrder(TreeNode root) {
        // 1. 定义返回结果列表
        List<List<Integer>> res = new LinkedList<>();
        // 2. 树为空 直接返回空列表
        if(root == null) return res;
        // 3. 定义队列 并将根节点入队
        Queue<TreeNode> queue = new ArrayDeque<>();
        queue.offer(root);
        // 4. 当队列不为空, 则循环遍历
        while (!queue.isEmpty()){
            // 5. 提前获取队列中元素个数, 千万不能在下面的循环条件中写 i < queue.size(), 因为一直在出队, size在不断变小
            int size = queue.size();
            // 6. 每一轮循环都重新定义一个临时的一维列表 用于存储这一层的节点值
            List<Integer> temp = new LinkedList<>();
            for(int i = 0; i < size; i++){
                // 7. 取出队头元素
                TreeNode node = queue.poll();
                // 8. 对这个节点进行处理, 这道题是层序遍历所有节点, 则是添加值进临时列表, 其他题就要看情况灵魂变动
                temp.add(node.val);
                // 9.左右孩子不为空 加入队列
                if(node.left != null) queue.offer(node.left);
                if(node.right != null) queue.offer(node.right);
            }
            // 10. 这一轮循环完, 将已经获取了一层节点值的临时列表添加进去结果列表
            res.add(temp);
        }
        return res;
    }

C++:

vector<vector<int>> levelOrder(TreeNode* root) {
    // 1. 定义返回结果列表
    vector<vector<int>> res;
    // 2. 树为空 直接返回空列表
    if(root == nullptr) return res;
    // 3. 定义队列 并将根节点入队
    queue<TreeNode*> queue;
    queue.push(root);
    // 4. 当队列不为空, 则循环遍历
    while (!queue.empty()){
        // 5. 提前获取队列中元素个数, 千万不能在下面的循环条件中写 i < queue.size(), 因为一直在出队, size在不断变小
        int size = queue.size();
        // 6. 每一轮循环都重新定义一个临时的一位列表 用于存储这一层的节点值
        vector<int> temp;
        for(int i = 0; i < size; i++){
            // 7. 取出队头元素
            TreeNode* node = queue.front();
            queue.pop();
            // 8. 对这个节点进行处理, 这道题是层序遍历所有节点, 则是添加值进临时列表, 其他题就要看情况灵魂变动
            temp.push_back(node->val);
            // 9.左右孩子不为空 加入队列
            if(node->left != nullptr) queue.push(node->left);
            if(node->right != nullptr) queue.push(node->right);
        }
        // 10. 这一轮循环完, 将已经获取了一层节点值的临时列表添加进去结果列表
        res.push_back(temp);
    }
    return res;
}

JS:

const levelOrder = function(root) {
    // 1. 定义返回结果列表
    let res = [];
    // 2. 树为空 直接返回空列表
    if(root == null) return res;
    // 3. 定义队列 并将根节点入队
    let queue = [];
    queue.push(root);
    // 4. 当队列不为空, 则循环遍历
    while (queue.length !== 0){
        // 5. 提前获取队列中元素个数, 千万不能在下面的循环条件中写 i < queue.size(), 因为一直在出队, size在不断变小
        let size = queue.length;
        // 6. 每一轮循环都重新定义一个临时的一位列表 用于存储这一层的节点值
        let temp = [];
        for(let i = 0; i < size; i++){
            // 7. 取出队头元素
            let node = queue.shift();
            // 8. 对这个节点进行处理, 这道题是层序遍历所有节点, 则是添加值进临时列表, 其他题就要看情况灵魂变动
            temp.push(node.val);
            // 9.左右孩子不为空 加入队列
            if(node.left != null) queue.push(node.left);
            if(node.right != null) queue.push(node.right);
        }
        // 10. 这一轮循环完, 将已经获取了一层节点值的临时列表添加进去结果列表
        res.push(temp);
    }
    return res;
};

第2题:107. 二叉树的层序遍历 II

1. 题目介绍

2.思路

就是第1题每一层的结果反过来, 真是无理取闹。

第一题的结果是[[3], [9, 20], [15, 7]], 我直接两极反转--->[[15, 7], [9, 20], [3]]

Collections.reverse(res);
return res;

闹剧结束了, 直接返回前将这个二维列表倒置即可,其他代码都不用改。

第3题:199. 二叉树的右视图

1. 题目介绍

2. 思路: 

还记得我说的很重要那一步吗, 取出队头之后做处理:

怎么看右视图, 这不就是要取每一行的最后一个节点吗?

我直接在取出队头节点之后判断, 最后一个才添加, 前两题是不挑的, 是个节点都要。

if (i == size - 1) res.add(node.val);

3. 代码

代码改动都不大, 就贴个Java吧。

public static List<Integer> rightSideView(TreeNode root) {
        List<Integer> res = new LinkedList<>();
        if (root == null)
            return res;
        Queue<TreeNode> queue = new ArrayDeque<>();
        queue.offer(root);
        while (!queue.isEmpty()) {
            int size = queue.size();
            for (int i = 0; i < size; i++) {
                TreeNode node = queue.poll();
                // 节点是这一层的最后一个才添加进结果列表
                if (i == size - 1) res.add(node.val);
                if (node.left != null)
                    queue.offer(node.left);
                if (node.right != null)
                    queue.offer(node.right);
            }
        }
        return res;
    }

第4题:637. 二叉树的层平均值

1. 题目介绍

2. 思路

要将每一层的节点的平均值添加进去结果列表

那我就先求和,再除以这一层的节点数目不就行了吗?

这道题注意好变量类型就行了:看上面示例的那么多00000了吗,成都人出的题, 我选择Double

3. 代码

public static List<Double> averageOfLevels(TreeNode root) {
        List<Double> res = new LinkedList<>();
        Queue<TreeNode> queue = new ArrayDeque<>();
        queue.offer(root);
        while (!queue.isEmpty()) {
            int size = queue.size();
            // 定义这一层的临时变量, 用来求这一层节点值的总和
            Double sum = (double) 0;
            for (int i = 0; i < size; i++) {
                TreeNode node = queue.poll();
                sum += node.val;
                if (node.left != null)
                    queue.offer(node.left);
                if (node.right != null)
                    queue.offer(node.right);
            }
            // 节点总和除以这一层的节点数目, 不就是这一层的平均值吗?
            res.add(sum / size);
        }
        return res;
    }

第5题:429. N 叉树的层序遍历

1. 题目介绍

2. 思路

还记得我上面最开始讲广度优先的关键思路了吗,都是一个套路:

 只需要在判断孩子是否为空那变一下就行了:

抓紧抬走!

// 从左往右依次判断它的每个孩子是否为空, 不为空添加进去队列
for (int j = 0; j < node.children.size(); j++){
   // 这是每一个孩子
   Node child = node.children.get(j);
   for(child != null){
      queue.offer(child);
   }
}

3. 代码

public static List<List<Integer>> levelOrder(Node root) {
        List<List<Integer>> res = new LinkedList<>();
        if (root == null)
            return res;
        Queue<Node> queue = new ArrayDeque<>();
        queue.offer(root);
        while (!queue.isEmpty()){
            int size = queue.size();
            List<Integer> temp = new LinkedList<>();
            for(int i = 0; i < size; i++){
                Node node = queue.poll();
                temp.add(node.val);
                // 从左往右依次判断它的每个孩子是否为空, 不为空添加进去队列
                for (int j = 0; j < node.children.size(); j++){
                    // 这是每一个孩子
                    Node child = node.children.get(j);
                    if(child != null){
                        queue.offer(child);
                    }
                }
            }
            res.add(temp);
        }
        return res;
    }

第6题:515. 在每个树行中找最大值

1. 题目介绍

2. 思路

无聊~还是同样的套路, 求最大值, 那么就先定义一个变量用来记录这一层的最大值, 每经过一个节点都判断是否比他大, 比我大就把老大的身份让给你。

if(maxValue < node.val){
   maxValue = node.val;
}

3. 代码

public static List<Integer> largestValues(TreeNode root) {
        List<Integer> res = new LinkedList<>();
        if(root == null) return res;
        Queue<TreeNode> queue = new ArrayDeque<>();
        queue.offer(root);
        while (!queue.isEmpty()){
            int size = queue.size();
            // 提前定义这个变量作为这一层的最大值, 先初始化为Integer的最小值
            int maxValue = Integer.MIN_VALUE;
            for(int i = 0; i < size; i++){
                TreeNode node = queue.poll();
                // 关键就在这儿啦, 比它大就更新
                if(maxValue < node.val){
                    maxValue = node.val;
                }
                if(node.left != null) queue.offer(node.left);
                if(node.right != null) queue.offer(node.right);
            }
            res.add(maxValue);
        }
        return res;
    }

第7题:116. 填充每个节点的下一个右侧节点指针

1. 题目介绍

2. 思路

这道题很不错, 总算跟前面那些一个模板的稍微有点不同了, 但是还是不难想到。

你是否会想:我怎么能够取出一个元素的时候,让他连接上这一层的下一个元素呢。

可能能想到这种方法;

当前节点,比如说示例的节点3。

如果它的前驱节点2,不是上一层的最后一个节点1,那么就能够将节点2的next赋值为节点3。

另外一种情况,取节点4,4的前一个节点是3,同时节点3也是4的上一层的最后一个节点,那就不管。

写写代码看看吧:

public static Node connect2(Node root) {
        if(root == null) return null;
        Queue<Node> queue = new ArrayDeque<>();
        queue.offer(root);
        // 每个节点的前面一个节点
        Node pre = new Node();
        // 上一层的最后一个节点
        Node lastEachLevel = new Node();
        while (!queue.isEmpty()){
            int size = queue.size();
            for(int i = 0; i < size; i++){
                Node node = queue.poll();
                // 如果这个节点的前面一个节点 不等于 上一层的最后一个节点
                if(pre != lastEachLevel){
                    // 连接!
                    pre.next = node;
                }
                // 此时当前节点将成为下一个节点的前驱节点
                pre = node;
                // 如果当前节点已经是最后一个节点
                if(i == size - 1){
                    // 此时当前节点将会成为下一个节点的 上一层的最后一个节点
                    lastEachLevel = node;
                }
                if(node.left != null) queue.offer(node.left);
                if(node.right != null) queue.offer(node.right);
            }
        }
        return root;
    }

提交逝世看:嘿,还通过了。不过太麻烦了,还要记录两个节点。

 

 来个简单的吧:

在看第一题的时候,你是否发现了,节点9和20是同一层的节点,且节点20在节点9后面:

在当取出队头元素9的时候,节点20在哪里?节点20在队头!

此时取得队头元素20即可,别把它取出来,是取得。不是poll(),是peek()!

// node已经被取出队列 此时的队头是node的下一个节点, 直接连接即可
node.next = queue.peek();

 运行下:

错求了!

嗷嗷嗷,3怎么连接上4了,3是这一层的最后一个节点,在判断节点3之前,队列里面还有4和5呢!

3是什么节点,3是最后一个节点,那么当节点不是这一层的最后一个节点的时候才能够连接队头元素

改一下:过啦

// 如果这个节点不是当前层的最后一个节点
if(i < size - 1){
  // node已经被取出队列 此时的队头是node的下一个节点, 直接连接即可
  node.next = queue.peek();
}

3. 代码

public static Node connect(Node root) {
        if(root == null) return null;
        Queue<Node> queue = new ArrayDeque<>();
        queue.offer(root);
        while (!queue.isEmpty()){
            int size = queue.size();
            for(int i = 0; i < size; i++){
                Node node = queue.poll();
                // 如果这个节点不是当前层的最后一个节点
                if(i < size - 1){
                    // node已经被取出队列 此时的队头是node的下一个节点, 直接连接即可
                    node.next = queue.peek();
                }
                if(node.left != null) queue.offer(node.left);
                if(node.right != null) queue.offer(node.right);
            }
        }
        return root;
    }

第8题:117. 填充每个节点的下一个右侧节点指针

看都不用看,凑字数的,和第7题一模一样,同样的代码直接提交。

第9题:104. 二叉树的最大深度

1. 题目介绍

2. 思路

唉,简单题。求最大深度,这不就是while()循环次数吗?

public static int maxDepth_1(TreeNode root) {
        int res = 0;
        if (root == null) return 0;
        Queue<TreeNode> queue = new ArrayDeque<>();
        queue.offer(root);
        while (!queue.isEmpty()) {
            // while每循环一次, depth++
            res++;
            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);
            }
        }
        return res;
    }

这道题其实可以作为递归题的练手题目:

我们可以想下如何递归:

直接对节点的两个孩子都进行递归,如果节点为空,那么肯定深度为0

节点不为空,那就已经至少已经有个1了,此时再去dfs这个节点,依次类推。

给个简单例子:

1. 先直接dfs节点1, 节点不为空, 再dfs节点1的孩子2和3

2.1 此时dfs节点2,还是不为空,但是它是叶子节点了,此时dfs节点2的两个假孩子,即空节点。

dfs空节点肯定是没有返回值的。

再返回上一层:节点2,即使它还是dfs返回了0,但是节点2是真真实实存在的,给个1作为补偿吧。

2.2 作为和节点2一层的节点3, 由于节点3有孩子,那么可以多dfs一层,那么节点3的深度肯定是比节点2多一个的。

3. 再返回上一层, 对于节点1, 它的两个孩子都已经dfs过一遍了, 两个孩子返回的值差距1, 那么就哪个孩子返回得多, 就将哪个孩子的深度再加个1来返回。

dfs的核心思路就是,每往下一层, 深度都加1。

public static int maxDepth(TreeNode root) {
  int res = dfs(root);
  return res;
}
public static int dfs(TreeNode root){
  if(root == null) return 0;
  return Math.max(dfs(root.left) +1, dfs(root.right) + 1);
}

 写成单个函数:

public static int maxDepth(TreeNode root) {
  if(root == null) return 0;
  return Math.max(maxDepth(root.left) + 1, maxDepth(root.right) + 1);
}

第10题:111. 二叉树的最小深度

1. 题目介绍

2. 思路

哎哟,怎么刚做完最大深度, 又来最小深度了。

到底深度多小是最小?

嗷嗷哦嗷嗷,当第一次遇到叶子节点的时候,我直接返回不就行了吗?

记得每次循环, 返回结果res++

光速下班!晚安么么哒~

3. 代码

 public static int minDepth(TreeNode root) {
        if (root == null) return 0;
        int res = 0;
        Queue<TreeNode> queue = new ArrayDeque<>();
        queue.offer(root);
        while (!queue.isEmpty()) {
            int size = queue.size();
            // 每循环一次, 则这一层都要遍历完, 
            res++;
            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(node.left == null && node.right == null){
                    return res;
                }
            }
        }
        return res;
    }

这几道题都是层序遍历非常简单的题,相信做完之后有更深的理解。

同时层序遍历广度优先搜索算法最经典,最简单的实践。

要想学好广度优先搜索算法,还是多去做图论的题目,比如经典的岛屿问题:200. 岛屿数量

觉得我的分享有用的话,点赞收藏关注么么哒~

本猪咪不定时分享学习笔记,期望成为更好的自己!

宝宝们晚安好梦~

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值