Serialize and Deserialize Binary Tree (二元樹的序列化與反序列化) 🔴 Hard¶
📌 LeetCode #297 — 題目連結 | NeetCode 解說
1. 🧐 Problem Dissection (釐清問題)¶
題目要求設計兩個演算法:
serialize(root): 將一棵 Binary Tree 轉換成一個 string。deserialize(data): 將這個 string 轉換回原本的 Binary Tree。
序列化的格式由你決定,只要能保證還原即可。 LeetCode 通常使用 Level Order (BFS) 來表示,例如 [1,2,3,null,null,4,5],但你可以使用任何有效的格式 (Preorder DFS is usually easier)。
-
Input:
root = [1,2,3,null,null,4,5] -
Output: Same tree object.
- Constraints:
- \(0 <= nodes <= 10^4\)
- \(-1000 <= Node.val <= 1000\)
2. 🐢 Brute Force Approach (暴力解)¶
這題本身就是考設計。
- 你可以存成 XML, JSON,但太 verbose。
- 你可以存成
(1(2)(3(4)(5)))這種括號表示法 (Preorder)。
3. 💡 The "Aha!" Moment (優化)¶
最簡單且高效的方法是 Preorder DFS (Root -> Left -> Right)。
Serialize (Preorder):
- 遍歷樹。
- 如果是非空節點,append
val+,(Delimiter)。 - 如果是空節點,append
N+,(Null Marker)。 - Result String:
1,2,N,N,3,4,N,N,5,N,N,
Deserialize:
- 將 string 根據
,split 成 values queue/stream。 1-> Create Node(1). Recursively build left.- Next is
2-> Create Node(2). Recursively build left.- Next is
N-> Return null.
- Next is
- Recursively build right (of 2).
- Next is
N-> Return null.
- Next is
- Next is
- Recursively build right (of 1).
- Next is
3-> ...
- Next is
- 依此類推。
Why Preorder? 因為 Root 永遠在最前面,我們一讀就知道當前子樹的 Root 是誰,然後可以直接開始遞迴構造左子樹,剩下的就是右子樹。
🎬 Visualization (演算法視覺化)¶
4. 💻 Implementation (程式碼)¶
Approach: Preorder DFS¶
#include <string>
#include <sstream>
#include <queue>
#include <vector>
using namespace std;
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};
class Codec {
public:
// Encodes a tree to a single string.
string serialize(TreeNode* root) {
if (!root) return "N";
// Preorder: Root, Left, Right
// Use comma as delimiter
return to_string(root->val) + "," + serialize(root->left) + "," + serialize(root->right);
}
// Decodes your encoded data to tree.
TreeNode* deserialize(string data) {
stringstream ss(data);
string segment;
queue<string> q;
while (getline(ss, segment, ',')) {
q.push(segment);
}
return deserializeHelper(q);
}
private:
TreeNode* deserializeHelper(queue<string>& q) {
if (q.empty()) return nullptr;
string val = q.front();
q.pop();
if (val == "N") {
return nullptr;
}
TreeNode* node = new TreeNode(stoi(val));
node->left = deserializeHelper(q);
node->right = deserializeHelper(q);
return node;
}
};
Python Reference¶
# Definition for a binary tree node.
# class TreeNode(object):
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Codec:
def serialize(self, root):
res = []
def dfs(node):
if not node:
res.append("N")
return
res.append(str(node.val))
dfs(node.left)
dfs(node.right)
dfs(root)
return ",".join(res)
def deserialize(self, data):
vals = data.split(",")
self.i = 0
def dfs():
if self.i >= len(vals):
return None
token = vals[self.i]
self.i += 1
if token == "N":
return None
node = TreeNode(int(token))
node.left = dfs()
node.right = dfs()
return node
return dfs()
# Your Codec object will be instantiated and called as such:
# ser = Codec()
# deser = Codec()
# ans = deser.deserialize(ser.serialize(root))
5. 📝 Detailed Code Comments (詳細註解)¶
class Codec {
public:
// Encodes a tree to a single string.
// 使用 Preorder Traversal (前序遍歷)
// 每個節點值後面加一個逗號 ','
// 空節點用 "N" 表示
string serialize(TreeNode* root) {
if (root == nullptr) {
return "N";
}
// 遞迴拼接字串 (效率稍微低一點,但逻辑清晰。面試可用 stringstream 優化)
return to_string(root->val) + "," + serialize(root->left) + "," + serialize(root->right);
}
// Decodes your encoded data to tree.
// 先將字串 split 成一個 Queue
TreeNode* deserialize(string data) {
queue<string> q;
stringstream ss(data);
string s;
// Split by comma
while (getline(ss, s, ',')) {
q.push(s);
}
return helper(q);
}
private:
TreeNode* helper(queue<string>& q) {
if (q.empty()) return nullptr;
string s = q.front();
q.pop();
// 遇到 "N" 代表是空節點,回傳 nullptr
if (s == "N") {
return nullptr;
}
// 否則建立新節點
TreeNode* node = new TreeNode(stoi(s));
// 遞迴建立左右子樹 (利用 Preorder 的順序)
node->left = helper(q);
node->right = helper(q);
return node;
}
};
6. 📊 Rigorous Complexity Analysis (複雜度分析)¶
- Time Complexity: \(O(n)\)
- Serialize: 訪問每個節點一次。
- Deserialize: 處理每個 split 出來的 token 一次。
- Space Complexity: \(O(n)\)
- Serialize: String/Recursion Stack.
- Deserialize: Queue/Recursion Stack.
7. 💼 Interview Tips (面試技巧)¶
🎯 Follow-up 問題¶
面試官可能會問的延伸問題:
- N-ary Tree?
- 壓縮序列化?
🚩 常見錯誤 (Red Flags)¶
避免這些會讓面試官扣分的錯誤:
- ⚠️ 序列化格式不一致
- ⚠️ 反序列化指標管理錯誤
✨ 加分項 (Bonus Points)¶
這些會讓你脫穎而出:
- 💎 BFS vs DFS 方案比較
- 💎 null 節點的處理