Skip to content

Commit d40fce4

Browse files
committed
feat: add Serialize and Deserialize Binary Tree
1 parent 93834e5 commit d40fce4

File tree

8 files changed

+440
-2
lines changed

8 files changed

+440
-2
lines changed
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
{
2+
"problem_name": "serialize_and_deserialize_binary_tree",
3+
"solution_class_name": "Codec",
4+
"problem_number": "297",
5+
"problem_title": "Serialize and Deserialize Binary Tree",
6+
"difficulty": "Hard",
7+
"topics": "String, Tree, Depth-First Search, Breadth-First Search, Design, Binary Tree",
8+
"tags": ["grind-75"],
9+
"readme_description": "Serialization is the process of converting a data structure or object into a sequence of bits so that it can be stored in a file or memory buffer, or transmitted across a network connection link to be reconstructed later in the same or another computer environment.\n\nDesign an algorithm to serialize and deserialize a binary tree. There is no restriction on how your serialization/deserialization algorithm should work. You just need to ensure that a binary tree can be serialized to a string and this string can be deserialized to the original tree structure.\n\n**Clarification:** The input/output format is the same as how LeetCode serializes a binary tree. You do not necessarily need to follow this format, so please be creative and come up with different approaches yourself.",
10+
"readme_examples": [
11+
{
12+
"content": "![Example 1](https://assets.leetcode.com/uploads/2020/09/15/serdeser.jpg)\n\n```\nInput: root = [1,2,3,null,null,4,5]\nOutput: [1,2,3,null,null,4,5]\n```"
13+
},
14+
{ "content": "```\nInput: root = []\nOutput: []\n```" }
15+
],
16+
"readme_constraints": "- The number of nodes in the tree is in the range [0, 10^4].\n- -1000 <= Node.val <= 1000",
17+
"readme_additional": "",
18+
"solution_imports": "from leetcode_py import TreeNode",
19+
"solution_methods": [
20+
{ "name": "__init__", "parameters": "", "return_type": "", "dummy_return": "" },
21+
{
22+
"name": "serialize",
23+
"parameters": "root: TreeNode | None",
24+
"return_type": "str",
25+
"dummy_return": "''"
26+
},
27+
{
28+
"name": "deserialize",
29+
"parameters": "data: str",
30+
"return_type": "TreeNode | None",
31+
"dummy_return": "None"
32+
}
33+
],
34+
"test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom leetcode_py import TreeNode\nfrom .solution import Codec",
35+
"test_class_name": "SerializeAndDeserializeBinaryTree",
36+
"test_helper_methods": [
37+
{ "name": "setup_method", "parameters": "", "body": "self.codec = Codec()" }
38+
],
39+
"test_methods": [
40+
{
41+
"name": "test_serialize_deserialize",
42+
"parametrize": "root_list",
43+
"parametrize_typed": "root_list: list[int | None]",
44+
"test_cases": "[([1, 2, 3, None, None, 4, 5]), ([]), ([1]), ([1, 2]), ([1, None, 2]), ([1, 2, 3, 4, 5, 6, 7]), ([5, 2, 3, None, None, 2, 4, 3, 1])]",
45+
"body": "root = TreeNode.from_list(root_list) if root_list else None\nserialized = self.codec.serialize(root)\ndeserialized = self.codec.deserialize(serialized)\nif root is None:\n assert deserialized is None\nelse:\n assert deserialized is not None\n assert deserialized.to_list() == root.to_list()"
46+
}
47+
],
48+
"playground_imports": "from solution import Codec\nfrom leetcode_py import TreeNode",
49+
"playground_test_case": "# Example test case\nroot_list = [1, 2, 3, None, None, 4, 5]\nroot = TreeNode.from_list(root_list) if root_list else None",
50+
"playground_execution": "codec = Codec()\nserialized = codec.serialize(root)\ndeserialized = codec.deserialize(serialized)\nprint(f'Original: {root.to_list() if root else None}')\nprint(f'Serialized: {serialized}')\nprint(f'Deserialized: {deserialized.to_list() if deserialized else None}')\ndeserialized",
51+
"playground_assertion": "if root is None:\n assert deserialized is None\nelse:\n assert deserialized is not None\n assert deserialized.to_list() == root.to_list()"
52+
}

.templates/leetcode/{{cookiecutter.problem_name}}/solution.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ class {{cookiecutter.solution_class_name}}:
44
{%- for _, methods in cookiecutter._solution_methods | dictsort %}
55
{%- for method in methods %}
66
# Time: O(?)
7-
# Space: O(?){# TODO: add decorator // optional self. #}
7+
# Space: O(?){# TODO: add decorator // optional self. // optional return type // optional return #}
88
def {{method.name}}(self, {{method.parameters}}) -> {{method.return_type}}:
99
# TODO: Implement {{method.name}}{# TODO: add body #}
1010
return {{method.dummy_return}}

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
PYTHON_VERSION = 3.13
2-
PROBLEM ?= word_ladder
2+
PROBLEM ?= serialize_and_deserialize_binary_tree
33
FORCE ?= 0
44
COMMA := ,
55

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Serialize and Deserialize Binary Tree
2+
3+
**Difficulty:** Hard
4+
**Topics:** String, Tree, Depth-First Search, Breadth-First Search, Design, Binary Tree
5+
**Tags:** grind-75
6+
7+
**LeetCode:** [Problem 297](https://leetcode.com/problems/serialize-and-deserialize-binary-tree/description/)
8+
9+
## Problem Description
10+
11+
Serialization is the process of converting a data structure or object into a sequence of bits so that it can be stored in a file or memory buffer, or transmitted across a network connection link to be reconstructed later in the same or another computer environment.
12+
13+
Design an algorithm to serialize and deserialize a binary tree. There is no restriction on how your serialization/deserialization algorithm should work. You just need to ensure that a binary tree can be serialized to a string and this string can be deserialized to the original tree structure.
14+
15+
**Clarification:** The input/output format is the same as how LeetCode serializes a binary tree. You do not necessarily need to follow this format, so please be creative and come up with different approaches yourself.
16+
17+
## Examples
18+
19+
### Example 1:
20+
21+
![Example 1](https://assets.leetcode.com/uploads/2020/09/15/serdeser.jpg)
22+
23+
```
24+
Input: root = [1,2,3,null,null,4,5]
25+
Output: [1,2,3,null,null,4,5]
26+
```
27+
28+
### Example 2:
29+
30+
```
31+
Input: root = []
32+
Output: []
33+
```
34+
35+
## Constraints
36+
37+
- The number of nodes in the tree is in the range [0, 10^4].
38+
- -1000 <= Node.val <= 1000

leetcode/serialize_and_deserialize_binary_tree/__init__.py

Whitespace-only changes.
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "code",
5+
"execution_count": 1,
6+
"id": "imports",
7+
"metadata": {},
8+
"outputs": [],
9+
"source": [
10+
"from solution import Codec\n",
11+
"\n",
12+
"from leetcode_py import TreeNode"
13+
]
14+
},
15+
{
16+
"cell_type": "code",
17+
"execution_count": 2,
18+
"id": "setup",
19+
"metadata": {},
20+
"outputs": [],
21+
"source": [
22+
"# Example test case\n",
23+
"root_list = [1, 2, 3, None, None, 4, 5]\n",
24+
"root = TreeNode.from_list(root_list) if root_list else None"
25+
]
26+
},
27+
{
28+
"cell_type": "code",
29+
"execution_count": 3,
30+
"id": "execute",
31+
"metadata": {},
32+
"outputs": [
33+
{
34+
"name": "stdout",
35+
"output_type": "stream",
36+
"text": [
37+
"Original: [1, 2, 3, None, None, 4, 5]\n",
38+
"Serialized: 1,2,#,#,3,4,#,#,5,#,#\n",
39+
"Deserialized: [1, 2, 3, None, None, 4, 5]\n"
40+
]
41+
},
42+
{
43+
"data": {
44+
"text/html": [
45+
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n",
46+
"<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n",
47+
" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n",
48+
"<!-- Generated by graphviz version 13.1.2 (20250808.2320)\n",
49+
" -->\n",
50+
"<!-- Pages: 1 -->\n",
51+
"<svg width=\"170pt\" height=\"188pt\"\n",
52+
" viewBox=\"0.00 0.00 170.00 188.00\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n",
53+
"<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 184)\">\n",
54+
"<polygon fill=\"white\" stroke=\"none\" points=\"-4,4 -4,-184 166,-184 166,4 -4,4\"/>\n",
55+
"<!-- 0 -->\n",
56+
"<g id=\"node1\" class=\"node\">\n",
57+
"<title>0</title>\n",
58+
"<ellipse fill=\"none\" stroke=\"black\" cx=\"63\" cy=\"-162\" rx=\"27\" ry=\"18\"/>\n",
59+
"<text xml:space=\"preserve\" text-anchor=\"middle\" x=\"63\" y=\"-156.95\" font-family=\"Times,serif\" font-size=\"14.00\">1</text>\n",
60+
"</g>\n",
61+
"<!-- 1 -->\n",
62+
"<g id=\"node2\" class=\"node\">\n",
63+
"<title>1</title>\n",
64+
"<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-90\" rx=\"27\" ry=\"18\"/>\n",
65+
"<text xml:space=\"preserve\" text-anchor=\"middle\" x=\"27\" y=\"-84.95\" font-family=\"Times,serif\" font-size=\"14.00\">2</text>\n",
66+
"</g>\n",
67+
"<!-- 0&#45;&gt;1 -->\n",
68+
"<g id=\"edge1\" class=\"edge\">\n",
69+
"<title>0&#45;&gt;1</title>\n",
70+
"<path fill=\"none\" stroke=\"black\" d=\"M54.65,-144.76C50.42,-136.55 45.19,-126.37 40.42,-117.09\"/>\n",
71+
"<polygon fill=\"black\" stroke=\"black\" points=\"43.68,-115.79 36,-108.49 37.46,-118.99 43.68,-115.79\"/>\n",
72+
"</g>\n",
73+
"<!-- 2 -->\n",
74+
"<g id=\"node3\" class=\"node\">\n",
75+
"<title>2</title>\n",
76+
"<ellipse fill=\"none\" stroke=\"black\" cx=\"99\" cy=\"-90\" rx=\"27\" ry=\"18\"/>\n",
77+
"<text xml:space=\"preserve\" text-anchor=\"middle\" x=\"99\" y=\"-84.95\" font-family=\"Times,serif\" font-size=\"14.00\">3</text>\n",
78+
"</g>\n",
79+
"<!-- 0&#45;&gt;2 -->\n",
80+
"<g id=\"edge2\" class=\"edge\">\n",
81+
"<title>0&#45;&gt;2</title>\n",
82+
"<path fill=\"none\" stroke=\"black\" d=\"M71.35,-144.76C75.58,-136.55 80.81,-126.37 85.58,-117.09\"/>\n",
83+
"<polygon fill=\"black\" stroke=\"black\" points=\"88.54,-118.99 90,-108.49 82.32,-115.79 88.54,-118.99\"/>\n",
84+
"</g>\n",
85+
"<!-- 3 -->\n",
86+
"<g id=\"node4\" class=\"node\">\n",
87+
"<title>3</title>\n",
88+
"<ellipse fill=\"none\" stroke=\"black\" cx=\"63\" cy=\"-18\" rx=\"27\" ry=\"18\"/>\n",
89+
"<text xml:space=\"preserve\" text-anchor=\"middle\" x=\"63\" y=\"-12.95\" font-family=\"Times,serif\" font-size=\"14.00\">4</text>\n",
90+
"</g>\n",
91+
"<!-- 2&#45;&gt;3 -->\n",
92+
"<g id=\"edge3\" class=\"edge\">\n",
93+
"<title>2&#45;&gt;3</title>\n",
94+
"<path fill=\"none\" stroke=\"black\" d=\"M90.65,-72.76C86.42,-64.55 81.19,-54.37 76.42,-45.09\"/>\n",
95+
"<polygon fill=\"black\" stroke=\"black\" points=\"79.68,-43.79 72,-36.49 73.46,-46.99 79.68,-43.79\"/>\n",
96+
"</g>\n",
97+
"<!-- 4 -->\n",
98+
"<g id=\"node5\" class=\"node\">\n",
99+
"<title>4</title>\n",
100+
"<ellipse fill=\"none\" stroke=\"black\" cx=\"135\" cy=\"-18\" rx=\"27\" ry=\"18\"/>\n",
101+
"<text xml:space=\"preserve\" text-anchor=\"middle\" x=\"135\" y=\"-12.95\" font-family=\"Times,serif\" font-size=\"14.00\">5</text>\n",
102+
"</g>\n",
103+
"<!-- 2&#45;&gt;4 -->\n",
104+
"<g id=\"edge4\" class=\"edge\">\n",
105+
"<title>2&#45;&gt;4</title>\n",
106+
"<path fill=\"none\" stroke=\"black\" d=\"M107.35,-72.76C111.58,-64.55 116.81,-54.37 121.58,-45.09\"/>\n",
107+
"<polygon fill=\"black\" stroke=\"black\" points=\"124.54,-46.99 126,-36.49 118.32,-43.79 124.54,-46.99\"/>\n",
108+
"</g>\n",
109+
"</g>\n",
110+
"</svg>\n"
111+
],
112+
"text/plain": [
113+
"TreeNode([1, 2, 3, None, None, 4, 5])"
114+
]
115+
},
116+
"execution_count": 3,
117+
"metadata": {},
118+
"output_type": "execute_result"
119+
}
120+
],
121+
"source": [
122+
"codec = Codec()\n",
123+
"serialized = codec.serialize(root)\n",
124+
"deserialized = codec.deserialize(serialized)\n",
125+
"print(f\"Original: {root.to_list() if root else None}\")\n",
126+
"print(f\"Serialized: {serialized}\")\n",
127+
"print(f\"Deserialized: {deserialized.to_list() if deserialized else None}\")\n",
128+
"deserialized"
129+
]
130+
},
131+
{
132+
"cell_type": "code",
133+
"execution_count": 4,
134+
"id": "test",
135+
"metadata": {},
136+
"outputs": [],
137+
"source": [
138+
"if root is None:\n",
139+
" assert deserialized is None\n",
140+
"else:\n",
141+
" assert deserialized is not None\n",
142+
" assert deserialized.to_list() == root.to_list()"
143+
]
144+
}
145+
],
146+
"metadata": {
147+
"kernelspec": {
148+
"display_name": "leetcode-py-py3.13",
149+
"language": "python",
150+
"name": "python3"
151+
},
152+
"language_info": {
153+
"codemirror_mode": {
154+
"name": "ipython",
155+
"version": 3
156+
},
157+
"file_extension": ".py",
158+
"mimetype": "text/x-python",
159+
"name": "python",
160+
"nbconvert_exporter": "python",
161+
"pygments_lexer": "ipython3",
162+
"version": "3.13.7"
163+
}
164+
},
165+
"nbformat": 4,
166+
"nbformat_minor": 5
167+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
from leetcode_py import TreeNode
2+
3+
4+
class Codec:
5+
# Preorder with Null Markers
6+
# Time: O(n)
7+
# Space: O(n)
8+
def serialize(self, root: TreeNode | None) -> str:
9+
vals = []
10+
11+
def dfs(node: TreeNode | None):
12+
if not node:
13+
vals.append("#")
14+
return
15+
vals.append(str(node.val))
16+
dfs(node.left)
17+
dfs(node.right)
18+
19+
dfs(root)
20+
return ",".join(vals)
21+
22+
# Time: O(n)
23+
# Space: O(n)
24+
def deserialize(self, data: str) -> TreeNode | None:
25+
vals = iter(data.split(","))
26+
27+
def dfs():
28+
val = next(vals)
29+
if val == "#":
30+
return None
31+
node = TreeNode(int(val))
32+
node.left = dfs()
33+
node.right = dfs()
34+
return node
35+
36+
return dfs()
37+
38+
39+
# Binary Tree Serialization Techniques
40+
41+
# Example Tree:
42+
# 1
43+
# / \
44+
# 2 3
45+
# / \
46+
# 4 5
47+
48+
# 1. Preorder with Null Markers (This Implementation)
49+
# Visit: root → left → right, mark nulls with '#'
50+
# Result: "1,2,#,#,3,4,#,#,5,#,#"
51+
# Pros: Self-contained, unambiguous, O(n) reconstruction
52+
# Cons: Longer string due to null markers
53+
54+
# 2. Level-order (BFS) with Null Markers
55+
# Visit level by level, mark nulls with '#'
56+
# Result: "1,2,3,#,#,4,5"
57+
# Pros: Simple format like preorder, level-by-level intuitive
58+
# Cons: Still requires queue processing
59+
60+
# 3. Postorder with Null Markers
61+
# Visit: left → right → root
62+
# Result: "#,#,2,#,#,4,#,#,5,3,1"
63+
# Pros: Bottom-up reconstruction
64+
# Cons: Less intuitive than preorder
65+
66+
# 4. Inorder + Preorder (Two Arrays)
67+
# Inorder: [2,1,4,3,5], Preorder: [1,2,3,4,5]
68+
# Pros: Works for any binary tree structure
69+
# Cons: Requires two arrays, only works with unique values
70+
71+
# 5. Parenthetical Preorder
72+
# Same traversal as #1 but with parentheses format: value(left)(right)
73+
# Result: "1(2()())(3(4()())(5()()))"
74+
# Pros: Human readable structure, shows nesting clearly
75+
# Cons: Complex parsing, verbose
76+
77+
# 6. Parenthetical Postorder
78+
# Same traversal as #3 but with parentheses format: (left)(right)value
79+
# Result: "(()()2)((()()4)(()()5)3)1"
80+
# Pros: Bottom-up readable structure
81+
# Cons: Even more complex parsing

0 commit comments

Comments
 (0)