前順の 走査で 二分木を 文字列に して、 文字列から 同じ木に組み直す
二分木を
海外で定番の問題リスト 4 本には入っていません
問題
二分木をserialize と、deserialize をroot をtree_codec (JavaScript と Go では treeCodec) もtree_codec は、root を serialize でdeserialize でserialize した
- 文字列の
形は 次の 1 つに 決めます。 根、 左の 部分木、 右の 部分木の 順 (前順) に 節を 読み、 値を そのまま 数字で 書きます。 子が 無い ところは #と書きます。 項目どうしは ,で区切り、 空白は 入れません - 空の
木は #だけです - 各節は
val(値)、left(左の子)、 right(右の子) を 持ちます。 子が 無い ところは、 Python では None、JavaScript では null、Go では nilです。Go の 節は TreeNodeで、Val、Left、Rightと大文字で 始まります。 新しい 節は TreeNode(5)(JavaScript はnew TreeNode(5)、Go は &TreeNode{Val: 5}) で作れます - 節の
数は 0 以上 300 以下、 値は どれも -1000 以上 1000 以下です
例では、null です。
入力と出力の例
例 1
根が 5、
例 2
空の# 1 つです。
例 3
負の- を
面接での聞かれ方
木の
- 「二分木を
文字列に して、 元に 戻せるように してください。 形は 自由です」 - 形が
自由なので、 「なぜ その形で 元に 戻せると 言えますか?」と 理由を 聞かれます。 空の 子の 印が 無いと、 前順だけでは 形が 決まりません - 「段の
順 (BFS) でも 書けますか?」 「二分探索木なら印を 省けますか?」と 続く ことがあります。 二分探索木は 値の 大小で 形が 決まるので、 前順の 値だけで 組み直せます
シニアの
ヒント
まだ開いていません
付箋は 4 枚。下の段ほど答えに近づきます
ヒント 2
ヒント 3
ヒント 4答えに近い
アルゴリズムのチートシートの「再帰と木」に、
関わる学び
再帰は、
まず 考える手順
段 1
前順で[5, 3, 8, null, 4] も [5, 3, 8, 4] も 5、# を
段 2
serialize は# を, で
段 3
deserialize は、, で# なら
段 4
読む位置は、
解答
Python
Python
def serialize(root):
out = []
def walk(n):
if n is None:
out.append("#")
return
out.append(str(n.val))
walk(n.left)
walk(n.right)
walk(root)
return ",".join(out)
def deserialize(data):
items = data.split(",")
it = iter(items)
def build():
item = next(it)
if item == "#":
return None
n = TreeNode(int(item))
n.left = build()
n.right = build()
return n
return build()
def tree_codec(root):
text = serialize(root)
back = deserialize(text)
return serialize(back)JavaScript
JavaScript
function serialize(root) {
const out = [];
const walk = (node) => {
if (node === null) {
out.push("#");
return;
}
out.push(String(node.val));
walk(node.left);
walk(node.right);
};
walk(root);
return out.join(",");
}
function deserialize(data) {
const items = data.split(",");
let i = 0;
const build = () => {
const item = items[i++];
if (item === "#") {
return null;
}
const node = new TreeNode(
Number(item)
);
node.left = build();
node.right = build();
return node;
};
return build();
}
function treeCodec(root) {
const text = serialize(root);
const back =
deserialize(text);
return serialize(back);
}Go
Go
import (
"strconv"
"strings"
)
func serialize(
root *TreeNode,
) string {
out := []string{}
var walk func(*TreeNode)
walk = func(n *TreeNode) {
if n == nil {
out = append(out, "#")
return
}
out = append(out,
strconv.Itoa(n.Val))
walk(n.Left)
walk(n.Right)
}
walk(root)
return strings.Join(out, ",")
}
func deserialize(
data string,
) *TreeNode {
items := strings.Split(
data, ",")
i := 0
var build func() *TreeNode
build = func() *TreeNode {
item := items[i]
i++
if item == "#" {
return nil
}
v, _ := strconv.Atoi(item)
n := &TreeNode{Val: v}
n.Left = build()
n.Right = build()
return n
}
return build()
}
func treeCodec(
root *TreeNode,
) string {
text := serialize(root)
back := deserialize(text)
return serialize(back)
}deserialize の build は、
計算量
- 時間は、
serializeもdeserializeも O(n) です。節ごとに 1 項目、 空の 子ごとに 1 項目を 書いて 読みます。 空の 子は n + 1 個なので、 項目は 全部で 2n + 1 個です - 使う
記憶は、 文字列と 項目の 並びに O(n)、 再帰の 呼び出しに O(h) です。 h は 木の 高さで、 1 本道の 木では O(n) に なります。 節が 300 以下なので、 Python の 再帰の 上限 (1000 段) には 届きません
よく ある間違い
- 空の
子の 印を 書かない。 前順の 値だけでは、 例 1 の 4 が 3 の 左か 右かが 分かりません。 空の 子を 全部 #で書いて 形を残します - 区切りを
入れずに 数字を並べる。 12 と 1、 2 の 区別が 付かなくなり、 負の 数の -もどこに 付くか分から なくなります - 読む位置を
再帰の 引数で 値として渡す。 左の 部分木で 進めた 位置が 呼び出し元に 戻らず、 右の 部分木を 同じ 所から 読み直します。 位置は 共有の 変数で持ちます - 書く
順と 読む順を変える。 前順で 書いたなら、 前順で 読みます。 段の 順で 書いて 前順で 読むと、 別の 木ができます
解答を動かした版 Python 3.14.7Node.js 24.22.1Go 1.24.2確認日 2026年11月8日
面接での 話し方の例
書き始める
「前順で
節を 書き、 空の 子は # と 書いて、 カンマで 区切ります。 空の 子まで 書くと 形が 文字列に 残るので、 読む側は 同じ 前順で 項目を 1 つずつ 取り、 # なら 空、 数なら節を 作って 左、 右の 順に 続きを 組めば元の 木に 戻ります。 どちらも O(n) です。」
書き終えたら、
「例 1 の
文字列を 読みます。 5 で 根を 作り、 左を 組みに 行って 3 を 作ります。 3 の 左は # で 空、 右は 4 で、 4 の 左右は # と # で 空です。 3 の 部分木が 終わったので、 根の 右に 戻り 8、 その 左右が # と # で、 項目を ちょうど 使い 切ります。」