LeetCode 297困难二叉树
二叉树的序列化与反序列化 图解题解
把一棵树变成字符串,还能原样还原——关键在空节点怎么处理。
给树拍「带空位说明书」的全家福:用前序遍历依次写下每个节点的值,遇到空孩子就写一个占位符 #——不能省,因为省了就不知道这里是空还是有子树。还原时照着说明书前序顺序读:读到值就建节点、递归建它的左右;读到 # 就停,返回空。序列化和反序列化消费顺序完全镜像,树结构可完美重建。
这道题到底在问什么
请设计一个算法来实现二叉树的序列化与反序列化。这里不限定你的序列 / 反序列化算法执行逻辑,你只需要保证一个二叉树可以被序列化为一个字符串并且将这个字符串反序列化为原始的树结构。 提示: 输入输出格式与 LeetCode 目前使用的方式一致,详情请参阅 LeetCode 序列化二叉树的格式 。你并非必须采取这种方式,你也可以采用其他的方法解决这个问题。
- root
- [1,2,3,null,null,4,5]
- 输出
- 可还原同一棵树
最优解:一步一步想明白
- 3下面 9 步动画会按主解代码推进,而不是泛泛讲题型。
- 4前序遍历,空节点也记成 #序列化就是把树变成一个字符串、之后还能一模一样还原。关键技巧:用前序遍历,并且把空节点也记成 #——这样结构才不会丢。
- 5访问即输出;遇空输出 #从根 1 开始前序走:输出 1,进左子树输出 2;2 是叶子、左右都空,于是补两个 #。串现在是 1,2,#,#。
- 6整棵树前序串 + 空标记回到根、进右子树,依次输出 3、4、5,每个叶子的空孩子都补 #。整棵树的序列就是:1,2,#,#,3,4,#,#,5,#,#。
- 7不记 # → 无法唯一还原如果不记空节点,光一串 1,2,3,4,5 没法区分树的结构——同一串能对应多棵不同的树。记上 #,每个节点的孩子是空还是子树就都标清了,才能唯一还原。
- 8读值建节点、读 # 返回空,再递归左右反序列化反着来:按前序顺序一个个读。读到值就建节点、再递归去建它的左右;读到 # 就返回空(递归出口)。消费顺序和当初序列化完全一致。
- 9前序消费完 → 结构完全重建按同样的前序顺序把整串消费完,每个 # 对应一个空、每个值对应一个节点——原树被完整重建。序列化与反序列化是一对互逆操作。
- 12记住这题的代码骨架:题意约束先落到状态变量,再用循环或递归维护它。
- 17把这题练到能复述后,再去同类题里迁移:先复用状态定义,再调整边界条件。
⚠️ 容易写错的地方
✗ 错:不记录空节点
✓ 对:空节点写 #
否则无法唯一还原结构
✗ 错:序列化和反序列化顺序不同
✓ 对:都用前序
编码协议必须一致
完整代码(Python / C++ / Java)
Python
class Codec:
def serialize(self, root):
vals = []
def dfs(node):
if not node:
vals.append('#')
return
vals.append(str(node.val))
dfs(node.left)
dfs(node.right)
dfs(root)
return ','.join(vals)
def deserialize(self, data):
vals = iter(data.split(','))
def dfs():
val = next(vals)
if val == '#':
return None
node = TreeNode(int(val))
node.left = dfs()
node.right = dfs()
return node
return dfs()C++
class Codec {
void ser(TreeNode* n, string& s) {
if (!n) { s += "#,"; return; }
s += to_string(n->val) + ",";
ser(n->left, s); ser(n->right, s);
}
TreeNode* des(istringstream& in) {
string v; getline(in, v, ',');
if (v == "#") return nullptr;
TreeNode* n = new TreeNode(stoi(v));
n->left = des(in); n->right = des(in);
return n;
}
public:
string serialize(TreeNode* root) { string s; ser(root, s); return s; }
TreeNode* deserialize(string data) { istringstream in(data); return des(in); }
};Java
public class Codec {
public String serialize(TreeNode root) {
StringBuilder sb = new StringBuilder(); ser(root, sb); return sb.toString();
}
void ser(TreeNode n, StringBuilder sb) {
if (n == null) { sb.append("#,"); return; }
sb.append(n.val).append(",");
ser(n.left, sb); ser(n.right, sb);
}
public TreeNode deserialize(String data) {
return des(new ArrayDeque<>(Arrays.asList(data.split(","))));
}
TreeNode des(Deque<String> q) {
String v = q.poll();
if (v.equals("#")) return null;
TreeNode n = new TreeNode(Integer.parseInt(v));
n.left = des(q); n.right = des(q);
return n;
}
}复杂度
时间复杂度
O(n)
每个节点和空指针处理一次
空间复杂度
O(n)
序列和递归栈
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 二叉树的序列化与反序列化 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
为什么前序遍历适合做序列化?+
前序「根→左→右」配上空节点标记,反序列化时按同样顺序消费就能唯一重建:先读根、再递归建左、再建右。
不标记空节点行不行?+
不行。光有非空节点的前序序列,无法确定每个节点的孩子是空还是有子树,同一序列可对应多棵树,无法唯一还原。
层序(BFS)也能序列化吗?+
能,LeetCode 官方格式就是层序 + null 占位。前序递归实现更简洁,层序需要队列。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。