和层序遍历一样按层收集,只是最终结果要"从下往上"排列:最底层放在结果最前面,根所在的层放在最后面,每层内部仍然从左到右。一个常见陷阱是图省事,把 BFS 过程中所有结点值拍平进一个数组再整体反转——这样不仅丢了"层"的分组(结果不再是数组的数组),还会把每层内部本该保留的从左到右顺序一起倒过来。本文用真实反例验证这个错误,给出正确写法、5 种语言实现,以及"先正常按层收集,最后只反转层顺序"的分步演示。
本文只用自己的话概括题意,完整约束以官方页面为准。和普通层序遍历几乎一样,只是最终结果要按"从下往上"的顺序排列:最底层放在结果数组最前面,根所在的层放在最后面;每一层内部仍然是从左到右。一共 5 组用例,覆盖:官方示例、空树、单结点、只有左孩子的链、完全二叉树:
[3,9,20,null,null,15,7] → [[15, 7], [9, 20], [3]] [] → [] [1] → [[1]] [1,2,null,3] → [[3], [2], [1]] [1,2,3,4,5,6,7] → [[4, 5, 6, 7], [2, 3], [1]]
一个容易踩的坑:既然要"整体倒过来",有人会图省事,直接把 BFS 过程中访问到的所有结点值拍平放进一个数组,最后把这个数组整体反转,以为这样就等价于"从下往上按层排列"。这其实丢了两件事:一是层与层之间该有的分组(结果不再是"数组的数组");二是反转整个扁平数组,会把每一层内部本该保留的"从左到右"顺序也一起倒过来:
输入 [3,9,20,null,null,15,7]\n正确写法(只反转层的顺序) → [[15, 7], [9, 20], [3]]\n陷阱写法(拍平后整体反转) → [7, 15, 20, 9, 3]
陷阱写法的输出甚至不是"数组的数组",形状就已经不对;即便忽略形状,把 [7, 15, 20, 9, 3] 按 2、2、1 重新分组也是 [[7, 15], [20, 9], [3]]——第一层本该是 [15, 7](从左到右),却变成了 [7, 15],第二层同理也被反转了内部顺序。正确的做法应该只反转"层"这个维度,每层内部收集的顺序不能动。
先用标准 BFS 按层收集,得到从上到下的 [[3], [9, 20], [15, 7]],每层内部仍是从左到右;最后只把最外层这个"层的列表"整体反转,得到 [[15, 7], [9, 20], [3]]。时间 O(n),空间 O(w)。Python、Java、Go、JavaScript、C++ 都是真实编译运行的,用同一组 5 个测试用例。
Python · 解法代码from collections import deque def level_order_bottom(root): if root is None: return [] result = [] q = deque([root]) while q: size = len(q) level = [] for _ in range(size): node = q.popleft() level.append(node.val) if node.left: q.append(node.left) if node.right: q.append(node.right) result.append(level) result.reverse() # 只反转"层"的顺序,每层内部仍是从左到右 return result
$ python3 solution.py [3,9,20,null,null,15,7] → [[15, 7], [9, 20], [3]] [] → [] [1] → [[1]] [1,2,null,3] → [[3], [2], [1]] [1,2,3,4,5,6,7] → [[4, 5, 6, 7], [2, 3], [1]]
Java · 解法代码class Solution { public List<List<Integer>> levelOrderBottom(TreeNode root) { LinkedList<List<Integer>> result = new LinkedList<>(); if (root == null) return result; Deque<TreeNode> q = new ArrayDeque<>(); q.add(root); while (!q.isEmpty()) { int size = q.size(); List<Integer> level = new ArrayList<>(); for (int i = 0; i < size; i++) { TreeNode node = q.poll(); level.add(node.val); if (node.left != null) q.add(node.left); if (node.right != null) q.add(node.right); } result.addFirst(level); // 只反转"层"的顺序,每层内部仍是从左到右 } return result; } }
$ javac -d build Main.java && java -cp build Main [3,9,20,null,null,15,7] → [[15, 7], [9, 20], [3]] [] → [] [1] → [[1]] [1,2,null,3] → [[3], [2], [1]] [1,2,3,4,5,6,7] → [[4, 5, 6, 7], [2, 3], [1]]
Go · 解法代码func levelOrderBottom(root *TreeNode) [][]int { result := [][]int{} if root == nil { return result } q := []*TreeNode{root} for len(q) > 0 { size := len(q) level := []int{} for i := 0; i < size; i++ { node := q[0] q = q[1:] level = append(level, node.Val) if node.Left != nil { q = append(q, node.Left) } if node.Right != nil { q = append(q, node.Right) } } result = append(result, level) } for i, j := 0, len(result)-1; i < j; i, j = i+1, j-1 { // 只反转"层"的顺序 result[i], result[j] = result[j], result[i] } return result }
$ go run solution.go [3,9,20,null,null,15,7] → [[15, 7], [9, 20], [3]] [] → [] [1] → [[1]] [1,2,null,3] → [[3], [2], [1]] [1,2,3,4,5,6,7] → [[4, 5, 6, 7], [2, 3], [1]]
JavaScript · 解法代码function levelOrderBottom(root) { const result = []; if (root === null) return result; let q = [root]; while (q.length) { const size = q.length; const level = []; for (let i = 0; i < size; i++) { const node = q.shift(); level.push(node.val); if (node.left) q.push(node.left); if (node.right) q.push(node.right); } result.push(level); } result.reverse(); // 只反转"层"的顺序,每层内部仍是从左到右 return result; }
$ node run.js [3,9,20,null,null,15,7] → [[15, 7], [9, 20], [3]] [] → [] [1] → [[1]] [1,2,null,3] → [[3], [2], [1]] [1,2,3,4,5,6,7] → [[4, 5, 6, 7], [2, 3], [1]]
C++ · 解法代码class Solution { public: vector<vector<int>> levelOrderBottom(TreeNode* root) { vector<vector<int>> result; if (!root) return result; queue<TreeNode*> q; q.push(root); while (!q.empty()) { int size = q.size(); vector<int> level; for (int i = 0; i < size; i++) { TreeNode* node = q.front(); q.pop(); level.push_back(node->val); if (node->left) q.push(node->left); if (node->right) q.push(node->right); } result.push_back(level); } reverse(result.begin(), result.end()); // 只反转"层"的顺序 return result; } };
$ clang++ -std=c++17 -O2 -o bin sol.cpp && ./bin [3,9,20,null,null,15,7] → [[15, 7], [9, 20], [3]] [] → [] [1] → [[1]] [1,2,null,3] → [[3], [2], [1]] [1,2,3,4,5,6,7] → [[4, 5, 6, 7], [2, 3], [1]]
共 5 组用例(含边界),以 Python 结果为基准逐组比对:\n Python 通过 5/5\n Java 通过 5/5\n Go 通过 5/5\n JavaScript 通过 5/5\n C++ 通过 5/5
Python 版本的 BFS 实现另外和一个独立写的 DFS 版本做了随机对拍:DFS 版按深度把结点分组(和普通层序遍历一样),遍历完成后再把"层的列表"整体反转。
随机对拍 30,000 组(结点数 0 到 10 的随机二叉树,值取 0-4),BFS 版与 DFS 版的结果全部一致,0 处不一致。
另外四种语言只在上面 5 组固定用例上验证过,没有做大规模随机对拍。
下面的演示用官方示例 [3,9,20,null,null,15,7]:先按标准 BFS 从上到下收集出 [[3], [9, 20], [15, 7]],每层内部保持从左到右;最后一步只把这个"层的列表"整体倒过来,变成 [[15, 7], [9, 20], [3]]。蓝色是本层正在处理的结点,绿色是已经处理完的结点。每一帧都是把上面的解法在这组输入上真实执行记录下来的。