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

77 阅读5分钟

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

题目描述

给你二叉树的根节点 root ,返回其节点值 自底向上的层序遍历 。 (即按从叶子节点所在层到根节点所在的层,逐层从左到右遍历)

难度

中等

题目链接

点击在LeetCode中查看题目

示例

示例 1:

示例1

输入:root = [3,9,20,null,null,15,7]
输出:[[15,7],[9,20],[3]]

示例 2:

输入:root = [1]
输出:[[1]]

示例 3:

输入:root = []
输出:[]

提示

  • 树中节点数目在范围 [0, 2000]
  • -1000 <= Node.val <= 1000

解题思路

方法:层序遍历 + 反转

这道题是第102题"二叉树的层序遍历"的变体,唯一的区别是要求自底向上返回层序遍历结果。

关键点:

  1. 先进行常规的层序遍历(从上到下)
  2. 最后将结果列表反转
  3. 也可以在遍历过程中将每层结果插入到结果列表的头部,避免最后的反转操作

具体步骤:

  1. 如果根节点为空,返回空列表
  2. 使用队列进行层序遍历:
    • 将根节点入队
    • 当队列不为空时,处理当前层的所有节点
    • 记录当前层的节点值
    • 将当前层节点的子节点入队
  3. 将最终结果列表反转(或在遍历过程中使用头插法)
  4. 返回结果

时间复杂度:O(n),其中n是树中节点的数量 空间复杂度:O(n),队列中最多存储n个节点

图解思路

层序遍历过程分析表

步骤队列状态当前层节点当前结果
初始[3]-[]
第1层[9,20][3][[3]]
第2层[15,7][9,20][[3],[9,20]]
第3层[][15,7][[3],[9,20],[15,7]]
反转--[[15,7],[9,20],[3]]

两种实现方式对比

实现方式遍历过程最终处理优缺点
反转法常规层序遍历反转整个结果列表实现简单,需要额外的反转操作
头插法常规层序遍历每层结果插入到结果列表头部无需最后反转,但插入操作可能较慢

代码实现

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 {
    // 方法一:层序遍历后反转结果
    public IList<IList<int>> LevelOrderBottom(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);
        }
        
        // 反转结果列表
        result.Reverse();
        
        return result;
    }
    
    // 方法二:使用头插法
    public IList<IList<int>> LevelOrderBottomInsertAtHead(TreeNode root) {
        LinkedList<IList<int>> result = new LinkedList<IList<int>>();
        
        if (root == null) {
            return result.ToList();
        }
        
        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.AddFirst(currentLevel);
        }
        
        return result.ToList();
    }
}

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:
    # 方法一:层序遍历后反转结果
    def levelOrderBottom(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[::-1]
    
    # 方法二:使用头插法
    def levelOrderBottomInsertAtHead(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.insert(0, current_level)
        
        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:
    // 方法一:层序遍历后反转结果
    vector<vector<int>> levelOrderBottom(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);
        }
        
        // 反转结果列表
        reverse(result.begin(), result.end());
        
        return result;
    }
    
    // 方法二:使用头插法
    vector<vector<int>> levelOrderBottomInsertAtHead(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.insert(result.begin(), currentLevel);
        }
        
        return result;
    }
};

执行结果

C# 实现

  • 执行用时:96 ms
  • 内存消耗:41.2 MB

Python 实现

  • 执行用时:36 ms
  • 内存消耗:16.8 MB

C++ 实现

  • 执行用时:4 ms
  • 内存消耗:12.5 MB

性能对比

语言执行用时内存消耗特点
C#96 ms41.2 MB代码结构清晰,使用内置的Reverse方法
Python36 ms16.8 MB使用切片操作[::-1]反转列表,简洁高效
C++4 ms12.5 MB执行效率最高,内存占用最小

代码亮点

  1. 🎯 利用队列实现高效的层序遍历
  2. 💡 提供两种实现方式:反转法和头插法
  3. 🔍 精确处理每一层的节点,确保层序关系正确
  4. 🎨 代码结构清晰,变量命名规范,易于理解

常见错误分析

  1. 🚫 忘记处理根节点为空的边界情况
  2. 🚫 层序遍历后忘记反转结果列表
  3. 🚫 使用头插法时,插入操作实现不正确
  4. 🚫 队列操作不当,导致层序关系混乱

解法对比

解法时间复杂度空间复杂度优点缺点
反转法O(n)O(n)实现简单,直观需要额外的反转操作
头插法O(n)O(n)无需最后反转插入操作可能较慢,尤其在数组实现中
DFS递归O(n)O(h)可直接构建结果实现复杂,需要额外处理层级关系

相关题目