【力扣-102.二叉树的层序遍历】Python笔记

0 阅读5分钟

二叉树的层序遍历:别再死记硬背了,掌握这个“套路”走遍天下

摘要:LeetCode 102. 二叉树的层序遍历,作为BFS算法的“祖师爷”级题目,你真的掌握了吗?本文带你拆解“队列+循环”的标准解法,理解为什么要记录每一层的大小,助你彻底攻克二叉树遍历!


前言

哈喽大家好,我是爱摸鱼的打工仔

今天我们来聊一道二叉树领域的“守门员”题目——LeetCode 102. 二叉树的层序遍历

如果说前序、中序、后序遍历是二叉树的“内功心法”,那么层序遍历就是行走江湖必备的“轻功”。它不仅在面试中出现频率极高,更是理解广度优先搜索(BFS)的基础。

很多小伙伴看到这题,第一反应是:“简单,建个队列扔进去不就行了?” 结果写代码时发现: “哎?我怎么把每一层的数据分开存啊?”

别急,今天我就带大家把这个“分层”的逻辑彻底理顺,保证你以后遇到类似的题目(比如求树的深度、按层打印),都能信手拈来!


核心知识点:队列与广度优先搜索

在动手写代码之前,我们先要建立一个直观的物理模型。

1. 什么是层序遍历?
想象一下,你站在树的根部,你的目光像扫描仪一样,从上到下、从左到右一层一层地扫描这棵树。

2. 为什么用队列?
队列的特点是“先进先出”。

  • 我们先访问根节点,然后需要访问它的左孩子和右孩子。
  • 为了保证“从左到右”的顺序,我们必须先把左孩子放进一个容器,再放右孩子。
  • 处理完根节点后,我们自然就要处理刚才放进去的左孩子。
  • 这不就是队列吗?

3. 难点:如何分层?
这是本题的精髓。队列里可能会同时存在第 2 层和第 3 层的节点。我们怎么知道第 2 层的节点什么时候处理完了呢?

秘诀就是:在进入每一层循环前,先记录一下当前队列的长度! 这个长度,就是当前层的节点总数。


解题思路:BFS 标准模板

我们的策略是:利用队列进行广度优先搜索,并通过控制循环次数来实现“分层”。

具体步骤:

  1. 边界检查:如果树是空的,直接返回空列表。

  2. 初始化:创建一个队列,把根节点扔进去。

  3. 外层循环:只要队列不为空,说明还有层没遍历完,继续循环。

  4. 关键一步(记录层大小) :获取当前队列的长度 level_size。这代表当前层有多少个节点。

  5. 内层循环:循环 level_size 次。

    • 从队列头部弹出一个节点。
    • 把它的值存入当前层的临时列表。
    • 如果它有左孩子,把左孩子入队。
    • 如果它有右孩子,把右孩子入队。
  6. 收尾:内层循环结束后,当前层的所有节点都处理完了,把临时列表存入最终结果。


代码详解

来看看具体的代码实现:

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right

# 引入双端队列,这是Python实现队列的高效方式
from collections import deque
from typing import List, Optional

class Solution:
    def levelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
        # 1. 边界处理:如果根节点为空,直接返回空列表,防止后续报错
        if not root:
            return []
        
        # 2. 初始化队列,将根节点入队
        # 使用 deque 是因为它的 popleft() 操作是 O(1) 复杂度,比普通列表快
        queue = deque([root])
        
        # 用于存储最终结果,格式是 List[List[int]]
        res = []
        
        # 3. 外层循环:只要队列里还有节点,就说明还有层没遍历
        while queue:
            # 4. 【核心步骤】记录当前层的节点数
            # 这一步至关重要!它锁定了当前这一层有多少个节点
            # 防止在循环中把下一层的节点混进来一起处理
            level_size = len(queue)
            
            # 初始化一个列表,专门存当前这一层的节点值
            level = []
            
            # 5. 内层循环:只处理当前层的节点(共 level_size 个)
            for _ in range(level_size):
                # 7. 出队队首节点(FIFO)
                node = queue.popleft()
                
                # 8. 将节点值加入当前层结果
                level.append(node.val)
                
                # 9. 如果有左孩子,入队左孩子(为下一层做准备)
                if node.left:
                    queue.append(node.left)
                
                # 10. 如果有右孩子,入队右孩子(为下一层做准备)
                if node.right:
                    queue.append(node.right)
            
            # 11. 当前层遍历结束,将这一层的结果加入最终结果列表
            res.append(level)
        
        # 12. 返回最终结果
        return res

深度解析:为什么要 for _ in range(level_size)

这是新手最容易晕的地方。

如果不加这个 level_size 限制,直接 while queue 然后不停地 popleftappend,会发生什么?

你会发现,你把整棵树的所有节点都塞进了一个列表里,变成了 [3, 9, 20, 15, 7],这就变成了“二叉树的扁平化遍历”,而不是“分层遍历”。

level_size 就像是一个“关卡”:

  • 刚开始,队列里只有 3level_size 是 1。我们只处理 1 次,把 3 拿出来,把 9, 20 放进去。

  • 下一轮循环,队列里是 [9, 20]level_size 是 2。我们强制自己只处理 2 次。

    • 第 1 次拿出 9,它没孩子,不放新节点。
    • 第 2 次拿出 20,它有两个孩子 15, 7,放进去。
  • 此时这一轮 for 循环结束,我们把 [9, 20] 这个列表存起来。完美分层!


复杂度分析

  • 时间复杂度O(N)O(N)。每个节点进队一次,出队一次,我们访问了每个节点恰好一次。
  • 空间复杂度: O(W) ,其中 WW 是树的最大宽度。队列中存储的节点数最多也就是树最宽的那一层的节点数。在最坏情况下(完全二叉树),空间复杂度接近 O(N) 。

总结

二叉树的层序遍历是 BFS 算法的基础模板。只要记住了 “队列 + 记录当前层大小” 这个套路,无论是求树的深度,还是锯齿形遍历(Zigzag),你都能轻松搞定。

我是爱摸鱼的打工仔,如果你觉得这篇文章对你有帮助,记得点赞收藏哦!我们下期见!