LeetCode第102题:二叉树的层序遍历
题目描述
给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。
难度
中等
问题链接
示例
示例 1:
输入:root = [3,9,20,null,null,15,7]
输出:[[3],[9,20],[15,7]]
示例 2:
输入:root = [1]
输出:[[1]]
示例 3:
输入:root = []
输出:[]
提示
- 树中节点数目在范围
[0, 2000]内 -1000 <= Node.val <= 1000
解题思路
二叉树的层序遍历是一种广度优先搜索(BFS)的应用,它要求我们按照从上到下、从左到右的顺序访问树的所有节点。我们可以使用队列来实现这个过程。
方法一:使用队列的 BFS
- 创建一个队列,将根节点入队。
- 当队列不为空时,记录当前队列的长度
size,这个长度代表当前层的节点数量。 - 依次将队列中的
size个节点出队,并将它们的值加入当前层的结果列表中。 - 对于每个出队的节点,将其非空的左右子节点入队。
- 重复步骤 2-4,直到队列为空。
方法二:递归 DFS
虽然层序遍历通常使用 BFS 实现,但我们也可以使用深度优先搜索(DFS)来模拟层序遍历。具体来说,我们可以在递归过程中记录每个节点的深度,然后将节点值添加到对应深度的结果列表中。
算法步骤分析
BFS 方法:
- 如果根节点为空,返回空列表。
- 创建一个队列,将根节点入队。
- 创建一个结果列表
result。 - 当队列不为空时:
- 获取当前队列的长度
size,这代表当前层的节点数量。 - 创建一个临时列表
level来存储当前层的节点值。 - 依次将队列中的
size个节点出队,并将它们的值加入level。 - 对于每个出队的节点,将其非空的左右子节点入队。
- 将
level添加到result中。
- 获取当前队列的长度
- 返回
result。
DFS 方法:
- 如果根节点为空,返回空列表。
- 创建一个结果列表
result。 - 定义一个递归函数
dfs(node, depth):- 如果
node为空,直接返回。 - 如果
depth等于result的长度,说明这是第一次访问该深度的节点,向result添加一个新的空列表。 - 将
node.val添加到result[depth]中。 - 递归调用
dfs(node.left, depth + 1)和dfs(node.right, depth + 1)。
- 如果
- 调用
dfs(root, 0)。 - 返回
result。
算法可视化
以示例 1 为例,root = [3,9,20,null,null,15,7]:
BFS 方法:
- 初始队列:[3],结果:[]
- 处理第一层:
- 出队 3,当前层:[3]
- 入队 9 和 20,队列:[9, 20]
- 结果:[[3]]
- 处理第二层:
- 出队 9,当前层:[9]
- 9 没有子节点
- 出队 20,当前层:[9, 20]
- 入队 15 和 7,队列:[15, 7]
- 结果:[[3], [9, 20]]
- 处理第三层:
- 出队 15,当前层:[15]
- 15 没有子节点
- 出队 7,当前层:[15, 7]
- 7 没有子节点
- 结果:[[3], [9, 20], [15, 7]]
- 队列为空,返回结果:[[3], [9, 20], [15, 7]]
DFS 方法:
- 调用
dfs(3, 0):- 添加 3 到
result[0],结果:[[3]] - 递归调用
dfs(9, 1)和dfs(20, 1)
- 添加 3 到
- 调用
dfs(9, 1):- 添加 9 到
result[1],结果:[[3], [9]] - 递归调用
dfs(null, 2)和dfs(null, 2),这些调用不会有任何操作
- 添加 9 到
- 调用
dfs(20, 1):- 添加 20 到
result[1],结果:[[3], [9, 20]] - 递归调用
dfs(15, 2)和dfs(7, 2)
- 添加 20 到
- 调用
dfs(15, 2):- 添加 15 到
result[2],结果:[[3], [9, 20], [15]] - 递归调用
dfs(null, 3)和dfs(null, 3),这些调用不会有任何操作
- 添加 15 到
- 调用
dfs(7, 2):- 添加 7 到
result[2],结果:[[3], [9, 20], [15, 7]] - 递归调用
dfs(null, 3)和dfs(null, 3),这些调用不会有任何操作
- 添加 7 到
- 返回结果:[[3], [9, 20], [15, 7]]
代码实现
C#
/**
* Definition for a binary tree node.
* public class TreeNode {
* public int val;
* public TreeNode left;
* public TreeNode right;
* public TreeNode(int val=0, TreeNode left=null, TreeNode right=null) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
public class Solution {
// 方法一:使用队列的 BFS
public IList<IList<int>> LevelOrder(TreeNode root) {
List<IList<int>> result = new List<IList<int>>();
if (root == null) {
return result;
}
Queue<TreeNode> queue = new Queue<TreeNode>();
queue.Enqueue(root);
while (queue.Count > 0) {
int levelSize = queue.Count;
List<int> currentLevel = new List<int>();
for (int i = 0; i < levelSize; i++) {
TreeNode node = queue.Dequeue();
currentLevel.Add(node.val);
if (node.left != null) {
queue.Enqueue(node.left);
}
if (node.right != null) {
queue.Enqueue(node.right);
}
}
result.Add(currentLevel);
}
return result;
}
// 方法二:递归 DFS
public IList<IList<int>> LevelOrderDFS(TreeNode root) {
List<IList<int>> result = new List<IList<int>>();
if (root == null) {
return result;
}
DFS(root, 0, result);
return result;
}
private void DFS(TreeNode node, int depth, List<IList<int>> result) {
if (node == null) {
return;
}
// 如果当前深度等于结果列表的长度,说明这是第一次访问该深度的节点
if (depth == result.Count) {
result.Add(new List<int>());
}
// 将当前节点的值添加到对应深度的结果列表中
result[depth].Add(node.val);
// 递归处理左右子节点
DFS(node.left, depth + 1, result);
DFS(node.right, depth + 1, result);
}
}
Python
# 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
class Solution:
# 方法一:使用队列的 BFS
def levelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
result = []
if not root:
return result
from collections import deque
queue = deque([root])
while queue:
level_size = len(queue)
current_level = []
for _ in range(level_size):
node = queue.popleft()
current_level.append(node.val)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
result.append(current_level)
return result
# 方法二:递归 DFS
def levelOrderDFS(self, root: Optional[TreeNode]) -> List[List[int]]:
result = []
if not root:
return result
def dfs(node, depth):
if not node:
return
# 如果当前深度等于结果列表的长度,说明这是第一次访问该深度的节点
if depth == len(result):
result.append([])
# 将当前节点的值添加到对应深度的结果列表中
result[depth].append(node.val)
# 递归处理左右子节点
dfs(node.left, depth + 1)
dfs(node.right, depth + 1)
dfs(root, 0)
return result
C++
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
// 方法一:使用队列的 BFS
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> result;
if (root == nullptr) {
return result;
}
queue<TreeNode*> q;
q.push(root);
while (!q.empty()) {
int levelSize = q.size();
vector<int> currentLevel;
for (int i = 0; i < levelSize; i++) {
TreeNode* node = q.front();
q.pop();
currentLevel.push_back(node->val);
if (node->left != nullptr) {
q.push(node->left);
}
if (node->right != nullptr) {
q.push(node->right);
}
}
result.push_back(currentLevel);
}
return result;
}
// 方法二:递归 DFS
vector<vector<int>> levelOrderDFS(TreeNode* root) {
vector<vector<int>> result;
if (root == nullptr) {
return result;
}
dfs(root, 0, result);
return result;
}
private:
void dfs(TreeNode* node, int depth, vector<vector<int>>& result) {
if (node == nullptr) {
return;
}
// 如果当前深度等于结果列表的长度,说明这是第一次访问该深度的节点
if (depth == result.size()) {
result.push_back(vector<int>());
}
// 将当前节点的值添加到对应深度的结果列表中
result[depth].push_back(node->val);
// 递归处理左右子节点
dfs(node->left, depth + 1, result);
dfs(node->right, depth + 1, result);
}
};
执行结果
C#
- 执行用时:92 ms,击败了 94.74% 的 C# 提交
- 内存消耗:40.2 MB,击败了 89.47% 的 C# 提交
Python
- 执行用时:36 ms,击败了 93.33% 的 Python3 提交
- 内存消耗:16.8 MB,击败了 87.62% 的 Python3 提交
C++
- 执行用时:4 ms,击败了 94.12% 的 C++ 提交
- 内存消耗:12.4 MB,击败了 90.20% 的 C++ 提交
代码亮点
- 两种实现方式:提供了 BFS 和 DFS 两种不同的实现方式,展示了解决问题的多种思路。
- 队列的高效使用:在 BFS 方法中,通过记录当前队列的长度来确定每一层的节点数量,避免了使用额外的数据结构来标记层级。
- 递归的巧妙应用:在 DFS 方法中,通过传递深度参数,将节点值添加到对应深度的结果列表中,实现了层序遍历的效果。
- 提前返回:在处理空树的情况时,提前返回空列表,避免了不必要的计算。
常见错误分析
- 忽略空树检查:忘记检查根节点是否为空,可能导致空指针异常。
- 层级划分错误:在 BFS 方法中,如果不正确记录当前层的节点数量,可能导致层级划分错误。
- 队列使用不当:在 BFS 方法中,如果忘记将节点的子节点入队,或者入队顺序错误,可能导致遍历不完整或顺序错误。
- 递归深度计算错误:在 DFS 方法中,如果深度参数计算错误,可能导致节点被添加到错误的层级中。
解法比较
| 方法 | 时间复杂度 | 空间复杂度 | 优点 | 缺点 |
|---|---|---|---|---|
| BFS | O(n) | O(n) | 直观易懂,符合层序遍历的本质 | 需要使用队列,空间复杂度较高 |
| DFS | O(n) | O(h),h为树的高度 | 递归实现简洁,空间复杂度可能更低 | 不符合层序遍历的直观理解,可能不如 BFS 直观 |