输出二叉树 图解题解
这道题到底在问什么
- 输入
- root=[1,2]
- 输出
- [["","1",""],["2","",""]] 高 1,矩阵 2 行 3 列
- 输入
- root=[1,2,3,null,4]
- 输出
- 4 落在 res[2][2] 高 2,矩阵 3 行 7 列
最优解:一步一步想明白
- 3记住「占区间、站中点、左右对半分」这句口诀,下面每一帧都在套它。
- 45 个节点这就是要画的树:根是 50,左孩子 30、右孩子 80;30 底下还挂着 10,80 底下挂着 90。一共 5 个节点,我们要把它们摆进矩阵。
- 5h = 2先量树高。从根一路往下数最长的那条路,50 到 30 再到 10,走了两条边,所以树高 h 等于 2。约定空节点高度是 −1,这样叶子的高度刚好是 0。
- 6m = 3 行, n = 7 列有了 h,矩阵尺寸就定了。行数 m 等于 h 加 1,也就是 3 行;列数 n 等于 2 的 (h+1) 次方再减 1,等于 2 的 3 次方减 1,也就是 7 列。接下来就在这张 3 行 7 列的网格上摆数。
- 73 × 7 全空先把这张 3 行 7 列的矩阵全部填上空串,得到一张干净的网格。现在从根开始,做一次 DFS 把节点一个个放进去。
- 8区间 [0, 6] → 中点 3轮到节点 50。它分到的列区间是 [0, 6](蓝色高亮这一段),中点等于 (0+6) 除以 2,也就是第 3 列。50 就站在这一格的正中间。
- 9res[0][3] = 50把 50 写进 res[0][3],这一格点亮。它还有孩子,接着要把区间对半分给左右子树。
- 10左半 [0, 2] | 右半 [4, 6]50 把自己的区间从中点劈开:左半段 [0, 2] 留给左孩子,右半段 [4, 6] 留给右孩子。两个孩子都掉到下一行,各自再去站自己那半段的中点。
- 11区间 [0, 2] → 中点 1轮到节点 30。它分到的列区间是 [0, 2](蓝色高亮这一段),中点等于 (0+2) 除以 2,也就是第 1 列。30 就站在这一格的正中间。
- 12res[1][1] = 30把 30 写进 res[1][1],这一格点亮。它还有孩子,接着要把区间对半分给左右子树。
- 13左半 [0, 0] | 右半 [2, 2]30 把自己的区间从中点劈开:左半段 [0, 0] 留给左孩子,右半段 [2, 2] 留给右孩子。两个孩子都掉到下一行,各自再去站自己那半段的中点。
- 14区间 [0, 0] → 中点 0轮到节点 10。它分到的列区间是 [0, 0](蓝色高亮这一段),中点等于 (0+0) 除以 2,也就是第 0 列。10 就站在这一格的正中间。
- 15res[2][0] = 10把 10 写进 res[2][0],这一格点亮。它是叶子,没有孩子,这条分支到底了。
- 16右半 [2, 2] 留空30 没有右孩子,右半段 [2, 2] 也就空着。可以看到这些空格正好对应树上缺失的子树位置。
- 17区间 [4, 6] → 中点 5轮到节点 80。它分到的列区间是 [4, 6](蓝色高亮这一段),中点等于 (4+6) 除以 2,也就是第 5 列。80 就站在这一格的正中间。
- 18res[1][5] = 80把 80 写进 res[1][5],这一格点亮。它还有孩子,接着要把区间对半分给左右子树。
- 19左半 [4, 4] | 右半 [6, 6]80 把自己的区间从中点劈开:左半段 [4, 4] 留给左孩子,右半段 [6, 6] 留给右孩子。两个孩子都掉到下一行,各自再去站自己那半段的中点。
- 20左半 [4, 4] 留空80 没有左孩子,所以左半段 [4, 4] 不放任何值,整段一直是空串。空的地方就是树里缺的那条枝。
- 21区间 [6, 6] → 中点 6轮到节点 90。它分到的列区间是 [6, 6](蓝色高亮这一段),中点等于 (6+6) 除以 2,也就是第 6 列。90 就站在这一格的正中间。
- 22res[2][6] = 90把 90 写进 res[2][6],这一格点亮。它是叶子,没有孩子,这条分支到底了。
- 235 个节点全部就位五个节点全部放完。每个值都站在自己区间的正中央,空格保持空串。这张矩阵就是树的格式化布局,从上往下、左右对称地排开。
- 24与答案逐格一致对照一下:第 0 行正中间是 50,第 1 行 30 和 80 对称地分在左右,第 2 行最外侧是 10 和 90。每一格都和正确答案对得上,DFS 区间取中点的办法奏效。
⚠️ 容易写错的地方
✗ 错:空节点高度记成 0
✓ 对:空节点高度记 −1
记成 0 会让树高、进而行列数全部多算一层
✗ 错:列数写成 2^h
✓ 对:列数是 2^(h+1) − 1
少了一倍宽度,右侧节点会越界或叠到一起
✗ 错:偏移量用固定值
✓ 对:偏移量是 2^(h−r−1),随层递减
越往下区间越窄,偏移必须跟着对半缩小
✗ 错:空格填 null 或空格字符
✓ 对:空格填空串 ""
题目明确要求空单元格是空字符串
完整代码(Python / C++ / Java)
Python
from __future__ import annotations
from typing import *
from collections import *
from functools import *
from itertools import *
from math import *
from heapq import *
from bisect import *
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
# 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 printTree(self, root: Optional[TreeNode]) -> List[List[str]]:
def height(root):
if root is None:
return -1
return 1 + max(height(root.left), height(root.right))
def dfs(root, r, c):
if root is None:
return
ans[r][c] = str(root.val)
dfs(root.left, r + 1, c - 2 ** (h - r - 1))
dfs(root.right, r + 1, c + 2 ** (h - r - 1))
h = height(root)
m, n = h + 1, 2 ** (h + 1) - 1
ans = [[""] * n for _ in range(m)]
dfs(root, 0, (n - 1) // 2)
return ansC++
#include <algorithm>
#include <array>
#include <cctype>
#include <climits>
#include <cmath>
#include <deque>
#include <functional>
#include <iostream>
#include <limits>
#include <map>
#include <numeric>
#include <optional>
#include <queue>
#include <set>
#include <sstream>
#include <stack>
#include <string>
#include <tuple>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
using namespace std;
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) {}
};
struct ListNode {
int val;
ListNode *next;
ListNode() : val(0), next(nullptr) {}
ListNode(int x) : val(x), next(nullptr) {}
ListNode(int x, ListNode *next) : val(x), next(next) {}
};
/**
* 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<string>> printTree(TreeNode* root) {
int h = height(root);
int m = h + 1, n = (1 << (h + 1)) - 1;
vector<vector<string>> ans(m, vector<string>(n, ""));
dfs(root, ans, h, 0, (n - 1) / 2);
return ans;
}
void dfs(TreeNode* root, vector<vector<string>>& ans, int h, int r, int c) {
if (!root) return;
ans[r][c] = to_string(root->val);
dfs(root->left, ans, h, r + 1, c - pow(2, h - r - 1));
dfs(root->right, ans, h, r + 1, c + pow(2, h - r - 1));
}
int height(TreeNode* root) {
if (!root) return -1;
return 1 + max(height(root->left), height(root->right));
}
};Java
import java.util.*;
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class TreeNode { int val; TreeNode left; TreeNode right; TreeNode(){} TreeNode(int val){this.val=val;} TreeNode(int val, TreeNode left, TreeNode right){this.val=val;this.left=left;this.right=right;} }
class ListNode { int val; ListNode next; ListNode(){} ListNode(int val){this.val=val;} ListNode(int val, ListNode next){this.val=val;this.next=next;} }
class Solution {
public List<List<String>> printTree(TreeNode root) {
int h = height(root);
int m = h + 1, n = (1 << (h + 1)) - 1;
String[][] res = new String[m][n];
for (int i = 0; i < m; ++i) {
Arrays.fill(res[i], "");
}
dfs(root, res, h, 0, (n - 1) / 2);
List<List<String>> ans = new ArrayList<>();
for (String[] t : res) {
ans.add(Arrays.asList(t));
}
return ans;
}
private void dfs(TreeNode root, String[][] res, int h, int r, int c) {
if (root == null) {
return;
}
res[r][c] = String.valueOf(root.val);
dfs(root.left, res, h, r + 1, c - (1 << (h - r - 1)));
dfs(root.right, res, h, r + 1, c + (1 << (h - r - 1)));
}
private int height(TreeNode root) {
if (root == null) {
return -1;
}
return 1 + Math.max(height(root.left), height(root.right));
}
}复杂度
时间
O(m × n)
矩阵有 m=(h+1) 行、n=2^(h+1)−1 列,建表与填空串就要 O(m·n);DFS 只访问每个节点一次,被填表开销盖过
空间
O(m × n)
结果矩阵本身占 O(m·n);递归栈深度是树高 O(h),相比矩阵更小
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 输出二叉树 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
为什么偏移量是 2^(h−r−1),会随层减半?+
因为每往下一层,一个节点能占的列区间就对半缩小。根占满整行,偏移最大;越往下区间越窄,孩子要站到自己那半段的正中,偏移自然跟着减半。r 越大,h−r−1 越小,2 的这个幂就越小。
用 DFS 还是 BFS 都行吗?+
都可以。关键是每放一个节点都要知道它的行 r 和列 c。DFS 递归时天然把 r、c 当参数传下去,最顺手;BFS 也行,但要在队列里额外存每个节点的列号或区间,写起来稍繁。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄声线把 输出二叉树 一步步讲透(全站 0 节语音精讲,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。