hot 100 第四十题 40.二叉树的层序遍历

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

示例 1:

复制代码
输入:root = [3,9,20,null,null,15,7]
输出:[[3],[9,20],[15,7]]

示例 2:

复制代码
输入:root = [1]
输出:[[1]]

示例 3:

复制代码
输入:root = []
输出:[]

这是二叉树的层序遍历问题,按层从上到下、从左到右遍历二叉树。

核心思路

BFS(广度优先搜索)+ 队列:逐层遍历,每次处理完一层的所有节点后再处理下一层。

复制代码
二叉树:
    3
   / \
  9  20
    /  \
   15   7

层序遍历:
[[3], [9,20], [15,7]]

解法1:BFS(推荐)

代码

复制代码
class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> result = new ArrayList<>();
        if (root == null) return result;
        
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        
        while (!queue.isEmpty()) {
            int levelSize = queue.size();  // 当前层的节点数
            List<Integer> currentLevel = new ArrayList<>();
            
            // 处理当前层的所有节点
            for (int i = 0; i < levelSize; i++) {
                TreeNode node = queue.poll();
                currentLevel.add(node.val);
                
                // 将下一层节点加入队列
                if (node.left != null) {
                    queue.offer(node.left);
                }
                if (node.right != null) {
                    queue.offer(node.right);
                }
            }
            
            result.add(currentLevel);
        }
        
        return result;
    }
}
```

### 详细演示
```
二叉树:
      3
     / \
    9  20
      /  \
     15   7

初始:
queue = [3]
result = []

第1层:
------------------
levelSize = 1
currentLevel = []

i=0:
  poll: node=3
  add: 3 → currentLevel=[3]
  offer: 9, 20
  queue = [9, 20]

result.add([3])
result = [[3]]

第2层:
------------------
levelSize = 2
currentLevel = []

i=0:
  poll: node=9
  add: 9 → currentLevel=[9]
  无子节点
  queue = [20]

i=1:
  poll: node=20
  add: 20 → currentLevel=[9, 20]
  offer: 15, 7
  queue = [15, 7]

result.add([9, 20])
result = [[3], [9, 20]]

第3层:
------------------
levelSize = 2
currentLevel = []

i=0:
  poll: node=15
  add: 15 → currentLevel=[15]
  无子节点
  queue = [7]

i=1:
  poll: node=7
  add: 7 → currentLevel=[15, 7]
  无子节点
  queue = []

result.add([15, 7])
result = [[3], [9, 20], [15, 7]]

队列为空,循环结束
返回: [[3], [9, 20], [15, 7]]
```

## 图解BFS过程
```
二叉树:
      3
     / \
    9  20
      /  \
     15   7

层序遍历过程:

第1层: queue=[3]
       处理3,加入9和20
       结果: [3]

第2层: queue=[9,20]
       处理9(无子),处理20(加入15,7)
       结果: [9,20]

第3层: queue=[15,7]
       处理15(无子),处理7(无子)
       结果: [15,7]

最终: [[3], [9,20], [15,7]]
```

## 关键:为什么需要 levelSize?
```
如果不记录levelSize:

queue = [3]
poll 3, add 9,20
queue = [9, 20]
poll 9, queue = [20]
poll 20, add 15,7
queue = [15, 7]
...

问题:不知道哪些节点属于同一层!

使用levelSize:

queue = [3]
levelSize = 1  ← 第1层有1个节点
处理1次,得到 [3]

queue = [9, 20]
levelSize = 2  ← 第2层有2个节点
处理2次,得到 [9, 20]

每层的节点数 = 处理前的队列大小

解法2:DFS(递归)

思路

用递归遍历,传递当前层数,将节点加入对应层的列表。

代码

复制代码
class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> result = new ArrayList<>();
        dfs(root, 0, result);
        return result;
    }
    
    private void dfs(TreeNode node, int level, List<List<Integer>> result) {
        if (node == null) return;
        
        // 如果当前层还没有列表,创建一个
        if (level == result.size()) {
            result.add(new ArrayList<>());
        }
        
        // 将当前节点加入对应层
        result.get(level).add(node.val);
        
        // 递归处理左右子树(下一层)
        dfs(node.left, level + 1, result);
        dfs(node.right, level + 1, result);
    }
}
```

### DFS演示
```
二叉树:
      3
     / \
    9  20
      /  \
     15   7

递归调用树(前序遍历):

dfs(3, 0)
  result[0] = [3]
  
  ├─ dfs(9, 1)
  │   result[1] = [9]
  │   
  │   ├─ dfs(null, 2) 返回
  │   └─ dfs(null, 2) 返回
  │
  └─ dfs(20, 1)
      result[1] = [9, 20]
      
      ├─ dfs(15, 2)
      │   result[2] = [15]
      │   
      │   ├─ dfs(null, 3) 返回
      │   └─ dfs(null, 3) 返回
      │
      └─ dfs(7, 2)
          result[2] = [15, 7]
          
          ├─ dfs(null, 3) 返回
          └─ dfs(null, 3) 返回

最终 result = [[3], [9,20], [15,7]]

两种解法对比

解法 时间复杂度 空间复杂度 优点 缺点
BFS O(n) O(w) 直观,符合题意 需要队列
DFS O(n) O(h) 代码简洁 不够直观

w = 树最大宽度,h = 树高度

变体1:自底向上层序遍历

复制代码
class Solution {
    public List<List<Integer>> levelOrderBottom(TreeNode root) {
        List<List<Integer>> result = new ArrayList<>();
        if (root == null) return result;
        
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        
        while (!queue.isEmpty()) {
            int levelSize = queue.size();
            List<Integer> currentLevel = new ArrayList<>();
            
            for (int i = 0; i < levelSize; i++) {
                TreeNode node = queue.poll();
                currentLevel.add(node.val);
                
                if (node.left != null) queue.offer(node.left);
                if (node.right != null) queue.offer(node.right);
            }
            
            result.add(0, currentLevel);  // 插入到开头
        }
        
        return result;
    }
}

输出: [[15,7], [9,20], [3]]

变体2:锯齿形层序遍历

复制代码
class Solution {
    public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
        List<List<Integer>> result = new ArrayList<>();
        if (root == null) return result;
        
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        boolean leftToRight = true;  // 方向标志
        
        while (!queue.isEmpty()) {
            int levelSize = queue.size();
            List<Integer> currentLevel = new ArrayList<>();
            
            for (int i = 0; i < levelSize; i++) {
                TreeNode node = queue.poll();
                
                if (leftToRight) {
                    currentLevel.add(node.val);  // 正常添加
                } else {
                    currentLevel.add(0, node.val);  // 头部添加(反向)
                }
                
                if (node.left != null) queue.offer(node.left);
                if (node.right != null) queue.offer(node.right);
            }
            
            result.add(currentLevel);
            leftToRight = !leftToRight;  // 切换方向
        }
        
        return result;
    }
}

输出: [[3], [20,9], [15,7]]

边界情况

复制代码
// 1. 空树
root = null
返回 []

// 2. 单节点
root = [1]
返回 [[1]]

// 3. 只有左子树
    1
   /
  2
 /
3
返回 [[1], [2], [3]]

// 4. 只有右子树
1
 \
  2
   \
    3
返回 [[1], [2], [3]]

// 5. 完全二叉树
      1
     / \
    2   3
   / \ / \
  4  5 6  7
返回 [[1], [2,3], [4,5,6,7]]

测试用例

复制代码
// 测试1
root = [3,9,20,null,null,15,7]
      3
     / \
    9  20
      /  \
     15   7
输出: [[3],[9,20],[15,7]]

// 测试2
root = [1]
输出: [[1]]

// 测试3
root = []
输出: []

// 测试4
root = [1,2,3,4,null,null,5]
      1
     / \
    2   3
   /     \
  4       5
输出: [[1],[2,3],[4,5]]

常见错误

错误1:没有记录levelSize

复制代码
// ✗ 错误(无法区分层)
while (!queue.isEmpty()) {
    TreeNode node = queue.poll();
    // 所有节点混在一起
}

// ✓ 正确
while (!queue.isEmpty()) {
    int levelSize = queue.size();  // 记录当前层节点数
    for (int i = 0; i < levelSize; i++) {
        // 处理当前层
    }
}

错误2:在循环中使用queue.size()

复制代码
// ✗ 错误(size会变化)
for (int i = 0; i < queue.size(); i++) {
    TreeNode node = queue.poll();
    queue.offer(node.left);  // size增加了!
}

// ✓ 正确
int levelSize = queue.size();  // 先保存
for (int i = 0; i < levelSize; i++) {
    // ...
}

错误3:DFS时忘记创建新层

复制代码
// ✗ 错误(越界)
result.get(level).add(node.val);  // level可能不存在

// ✓ 正确
if (level == result.size()) {
    result.add(new ArrayList<>());
}
result.get(level).add(node.val);

复杂度分析

BFS

  • 时间: O(n) --- 每个节点访问一次
  • 空间 : O(w) --- 队列最大宽度
    • 完全二叉树最后一层: O(n/2)

DFS

  • 时间: O(n) --- 每个节点访问一次
  • 空间 : O(h) --- 递归栈深度
    • 平衡树: O(log n)
    • 链状树: O(n)

本质

层序遍历的核心:

BFS思想

  • 用队列存储待访问节点
  • 每次处理完一层再处理下一层
  • levelSize 是关键,标识每层边界

与DFS对比

  • DFS(前中后序):深度优先,用栈/递归
  • BFS(层序):广度优先,用队列

应用场景

  • 最短路径问题
  • 逐层处理的问题
  • 需要按层统计的问题
相关推荐
※DX3906※2 小时前
Java排序算法--全面详解面试中涉及的排序
java·开发语言·数据结构·面试·排序算法
木斯佳3 小时前
HarmonyOS 6实战:从爆款vlog探究鸿蒙智能体提取关键帧算法
算法·华为·harmonyos
Mr.朱鹏3 小时前
JVM-GC垃圾回收案例
java·jvm·spring boot·算法·spring·spring cloud·java-ee
WJSKad12354 小时前
【DepthPro】实战教程:单目深度估计算法详解与应用
算法
wzqllwy4 小时前
8 大经典排序算法(Java 实现):原理 + Demo + 核心分析
java·算法·排序算法
We་ct4 小时前
LeetCode 77. 组合:DFS回溯+剪枝,高效求解组合问题
开发语言·前端·算法·leetcode·typescript·深度优先·剪枝
重生之我是Java开发战士4 小时前
【递归、搜索与回溯】二叉树中的深度优先搜索:布尔二叉树,求根节点到叶节点数字之和,二叉树剪枝,验证二叉搜索树,第K小的元素,二叉树的所有路径
算法·深度优先·剪枝
篮l球场4 小时前
矩阵置零
算法
lihihi4 小时前
P1650 [ICPC 2004 Shanghai R] 田忌赛马(同洛谷2587)
开发语言·算法·r语言