From edae8702a285c3fba7a4cae32d25b1610a4be025 Mon Sep 17 00:00:00 2001 From: Kazuki Kijima Date: Tue, 10 Mar 2026 17:40:13 -0700 Subject: [PATCH 01/11] LeetCode 105: Add step 1 solution --- leetcode/105/memo.md | 15 +++++++++++++++ leetcode/105/step1.py | 24 ++++++++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 leetcode/105/memo.md create mode 100644 leetcode/105/step1.py diff --git a/leetcode/105/memo.md b/leetcode/105/memo.md new file mode 100644 index 0000000..b34418c --- /dev/null +++ b/leetcode/105/memo.md @@ -0,0 +1,15 @@ +# Step 1 + +先日、レビュー依頼を受けて取り組んだのだが、初見ではいくら例をこねくり回してもさっぱりわからなかった (アルゴリズムに落とし込めなかった)。 +前回取り組んだ際の私の理解では、 + +preorder では、左右の部分木より先に root が現れる。 +inorder では、左・root・右 の順序が保たれる。 + +これを組み合わせて + +preorder で見つけた root を inorder 内で探すと、その位置より左にある値は左の部分木、右にある値は右の部分木に属する。 + +これを再帰的に当てはめていけば、binary tree が一意に決まる。 + +未だしっくりこないし、この理解が正しいか自信はないが、他の方々のPRを見てみることにする。 diff --git a/leetcode/105/step1.py b/leetcode/105/step1.py new file mode 100644 index 0000000..b2154f1 --- /dev/null +++ b/leetcode/105/step1.py @@ -0,0 +1,24 @@ +# 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 buildTree(self, preorder: list[int], inorder: list[int]) -> TreeNode | None: + def build_tree_helper(pre_i: int, in_i: int, in_end: int) -> TreeNode | None: + if in_end <= in_i: + return None + + try: + matching_in_i = inorder.index(preorder[pre_i], in_i, in_end) + except ValueError: + return build_tree_helper(pre_i + 1, in_i, in_end) + + return TreeNode( + val=inorder[matching_in_i], + left=build_tree_helper(pre_i + 1, in_i, matching_in_i), + right=build_tree_helper(pre_i + 1, matching_in_i + 1, in_end), + ) + + return build_tree_helper(0, 0, len(inorder)) From d4792afadaca758bde590998a96cffa236e22476 Mon Sep 17 00:00:00 2001 From: Kazuki Kijima Date: Tue, 10 Mar 2026 21:29:19 -0700 Subject: [PATCH 02/11] LeetCode 105: Checked mamo3gr-san's PR --- leetcode/105/memo.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/leetcode/105/memo.md b/leetcode/105/memo.md index b34418c..85b1ca4 100644 --- a/leetcode/105/memo.md +++ b/leetcode/105/memo.md @@ -13,3 +13,13 @@ preorder で見つけた root を inorder 内で探すと、その位置より これを再帰的に当てはめていけば、binary tree が一意に決まる。 未だしっくりこないし、この理解が正しいか自信はないが、他の方々のPRを見てみることにする。 + +# Step 2 + +[mamo3grさんのPR](https://github.com/mamo3gr/arai60/pull/28/changes#diff-4298635e808820604d74d3c43a2bcce0a2e36a9627af25d32462a8c12e01beea) +- step 1 が私のものとほぼ同じに見えるが、インデックスを引き回さずコピーを渡すことで helper 関数を作らずに済んでいる。空間計算量は O(n^2) だが、今回の制約では大丈夫そうな範囲のように見受けられる。 +- hash map の使用で、inorder内のrootの位置を探す操作を短縮する工夫。 +- dataclass は個人的に普段あまり使用しないから思いつかなかった。ドキュメントを眺めてみよう。 + - [dataclasses - Data Classes - Python documentation](https://docs.python.org/3/library/dataclasses.html) + - "This function is a decorator that is used to add generated special methods to classes, as described below..." + From 5117b86bd70a123ed3e4d33286a83c0ccc5cd82f Mon Sep 17 00:00:00 2001 From: Kazuki Kijima Date: Tue, 10 Mar 2026 22:19:41 -0700 Subject: [PATCH 03/11] LeetCode 105: Add mamo3gr-san's solution (rewrite in my own language) --- leetcode/105/memo.md | 2 ++ leetcode/105/step2_span_based.py | 51 ++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 leetcode/105/step2_span_based.py diff --git a/leetcode/105/memo.md b/leetcode/105/memo.md index 85b1ca4..6f52441 100644 --- a/leetcode/105/memo.md +++ b/leetcode/105/memo.md @@ -22,4 +22,6 @@ preorder で見つけた root を inorder 内で探すと、その位置より - dataclass は個人的に普段あまり使用しないから思いつかなかった。ドキュメントを眺めてみよう。 - [dataclasses - Data Classes - Python documentation](https://docs.python.org/3/library/dataclasses.html) - "This function is a decorator that is used to add generated special methods to classes, as described below..." +- Span を使用する解法を写経させていただいた。やっと preorder の区間を次にどう渡していくのか、わかりかけてきた気がする。`step2_span_based.py` + - Time Complexity: O(n), Space Complexity: O(n) call stack in worst cases, hash map diff --git a/leetcode/105/step2_span_based.py b/leetcode/105/step2_span_based.py new file mode 100644 index 0000000..e160c47 --- /dev/null +++ b/leetcode/105/step2_span_based.py @@ -0,0 +1,51 @@ +# 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 +import dataclasses + + +@dataclasses.dataclass +class Span: + begin: int + end: int + + @property + def size(self) -> int: + return self.end - self.begin + + def is_empty(self) -> bool: + return self.size <= 0 + + +class Solution: + def buildTree(self, preorder: list[int], inorder: list[int]) -> TreeNode | None: + num_to_inorder_i = {num: i for i, num in enumerate(inorder)} + + def build_tree_helper( + preorder_span: Span, inorder_span: Span + ) -> TreeNode | None: + if preorder_span.is_empty(): + return None + + root_val = preorder[preorder_span.begin] + root_inorder_i = num_to_inorder_i[root_val] + + inorder_left = Span(inorder_span.begin, root_inorder_i) + inorder_right = Span(root_inorder_i + 1, inorder_span.end) + + preorder_left = Span( + preorder_span.begin + 1, preorder_span.begin + 1 + inorder_left.size + ) + preorder_right = Span( + preorder_span.end - inorder_right.size, preorder_span.end + ) + return TreeNode( + val=root_val, + left=build_tree_helper(preorder_left, inorder_left), + right=build_tree_helper(preorder_right, inorder_right), + ) + + return build_tree_helper(Span(0, len(preorder)), Span(0, len(inorder))) From 6bed66deb79a1a3bf8901c97b8efd76f95e54107 Mon Sep 17 00:00:00 2001 From: Kazuki Kijima Date: Thu, 12 Mar 2026 15:42:10 -0700 Subject: [PATCH 04/11] LeetCode 105: Add garunitule-san's solution --- leetcode/105/memo.md | 4 ++++ leetcode/105/step2_pick_preorder.py | 31 +++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 leetcode/105/step2_pick_preorder.py diff --git a/leetcode/105/memo.md b/leetcode/105/memo.md index 6f52441..c4d9a7e 100644 --- a/leetcode/105/memo.md +++ b/leetcode/105/memo.md @@ -25,3 +25,7 @@ preorder で見つけた root を inorder 内で探すと、その位置より - Span を使用する解法を写経させていただいた。やっと preorder の区間を次にどう渡していくのか、わかりかけてきた気がする。`step2_span_based.py` - Time Complexity: O(n), Space Complexity: O(n) call stack in worst cases, hash map +[garunituleさんのPR](https://github.com/garunitule/coding_practice/pull/29) +- 写経 -> `step2_pick_preorder.py` +- Step 1 を考えていた時はうまく言語化できていなかったのだが、私の Step 1 の方法では、次の root をつけるために preorder を毎回スキャンする必要があった。 +- この方法では、次に root になる preorder の位置を返して再帰間で共有できるため、余計な探索を省くことができていて、脳への収まりも良い。 diff --git a/leetcode/105/step2_pick_preorder.py b/leetcode/105/step2_pick_preorder.py new file mode 100644 index 0000000..e96ed14 --- /dev/null +++ b/leetcode/105/step2_pick_preorder.py @@ -0,0 +1,31 @@ +# 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 buildTree(self, preorder: list[int], inorder: list[int]) -> TrreeNode | None: + if not preorder or not inorder: + return None + + val_to_inorder_index = {num: i for i, num in enumerate(inorder)} + + def build_tree_helper( + preorder_index: int, inorder_begin: int, inorder_end: int + ) -> tuple[TreeNode | None, int]: + node = TreeNode(preorder[preorder_index]) + preorder_index += 1 + inorder_index = val_to_inorder_index[node.val] + if inorder_begin <= inorder_index - 1: + node.left, preorder_index = build_tree_helper( + preorder_index, inorder_begin, inorder_index - 1 + ) + if inorder_index + 1 <= inorder_end: + node.right, preorder_index = build_tree_helper( + preorder_index, inorder_index + 1, inorder_end + ) + + return node, preorder_index + + return build_tree_helper(0, 0, len(inorder) - 1)[0] From c6441da3c953cf0fdb867e513fdf56b011eee1ed Mon Sep 17 00:00:00 2001 From: Kazuki Kijima Date: Sun, 15 Mar 2026 17:30:10 -0700 Subject: [PATCH 05/11] LeetCode 105: Add step 3 solution with preorder pick --- leetcode/105/step3.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 leetcode/105/step3.py diff --git a/leetcode/105/step3.py b/leetcode/105/step3.py new file mode 100644 index 0000000..fd70064 --- /dev/null +++ b/leetcode/105/step3.py @@ -0,0 +1,34 @@ +# 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 buildTree(self, preorder: list[int], inorder: list[int]) -> TreeNode | None: + value_to_inorder_index = {index: value for value, index in enumerate(inorder)} + + def build_tree_helper( + root_preorder_index: int, inorder_begin: int, inorder_end: int + ) -> tuple[TreeNode | None, int]: + if inorder_end <= inorder_begin: + return None, root_preorder_index + + root_val = preorder[root_preorder_index] + root_inorder_index = value_to_inorder_index[root_val] + + next_root_preorder_index = root_preorder_index + 1 + + left, next_root_preorder_index = build_tree_helper( + next_root_preorder_index, inorder_begin, root_inorder_index + ) + right, next_root_preorder_index = build_tree_helper( + next_root_preorder_index, root_inorder_index + 1, inorder_end + ) + + return ( + TreeNode(val=root_val, left=left, right=right), + next_root_preorder_index, + ) + + return build_tree_helper(0, 0, len(inorder))[0] From 3aebe3445dc7ca743274ab9a216568aaea959008 Mon Sep 17 00:00:00 2001 From: Kazuki Kijima Date: Mon, 16 Mar 2026 11:39:59 -0700 Subject: [PATCH 06/11] LeetCode 105: Tweak step 3 solution --- leetcode/105/step3.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/leetcode/105/step3.py b/leetcode/105/step3.py index fd70064..bac9eca 100644 --- a/leetcode/105/step3.py +++ b/leetcode/105/step3.py @@ -6,7 +6,7 @@ # self.right = right class Solution: def buildTree(self, preorder: list[int], inorder: list[int]) -> TreeNode | None: - value_to_inorder_index = {index: value for value, index in enumerate(inorder)} + val_to_inorder_index = {val: index for index, val in enumerate(inorder)} def build_tree_helper( root_preorder_index: int, inorder_begin: int, inorder_end: int @@ -15,7 +15,7 @@ def build_tree_helper( return None, root_preorder_index root_val = preorder[root_preorder_index] - root_inorder_index = value_to_inorder_index[root_val] + root_inorder_index = val_to_inorder_index[root_val] next_root_preorder_index = root_preorder_index + 1 @@ -26,9 +26,6 @@ def build_tree_helper( next_root_preorder_index, root_inorder_index + 1, inorder_end ) - return ( - TreeNode(val=root_val, left=left, right=right), - next_root_preorder_index, - ) + return TreeNode(root_val, left, right), next_root_preorder_index return build_tree_helper(0, 0, len(inorder))[0] From 4ec33f84517a8a9018c68df82da40fcc2305f45b Mon Sep 17 00:00:00 2001 From: Kazuki Kijima Date: Mon, 16 Mar 2026 11:45:42 -0700 Subject: [PATCH 07/11] LeetCode 105: Add TODO comment --- leetcode/105/memo.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/leetcode/105/memo.md b/leetcode/105/memo.md index c4d9a7e..c2d9fc2 100644 --- a/leetcode/105/memo.md +++ b/leetcode/105/memo.md @@ -12,6 +12,8 @@ preorder で見つけた root を inorder 内で探すと、その位置より これを再帰的に当てはめていけば、binary tree が一意に決まる。 +TODO: 時間計算量・空間計算量 + 未だしっくりこないし、この理解が正しいか自信はないが、他の方々のPRを見てみることにする。 # Step 2 From e6595760f6e1c9c5e81b5e66bfe102164d963f57 Mon Sep 17 00:00:00 2001 From: Kazuki Kijima Date: Tue, 17 Mar 2026 16:15:42 -0700 Subject: [PATCH 08/11] LeetCode 105: Add small notes on pick root preorder index approach --- leetcode/105/memo.md | 6 ++++++ leetcode/105/{step3.py => step3_pick_next_root.py} | 0 2 files changed, 6 insertions(+) rename leetcode/105/{step3.py => step3_pick_next_root.py} (100%) diff --git a/leetcode/105/memo.md b/leetcode/105/memo.md index c2d9fc2..fcd681a 100644 --- a/leetcode/105/memo.md +++ b/leetcode/105/memo.md @@ -31,3 +31,9 @@ TODO: 時間計算量・空間計算量 - 写経 -> `step2_pick_preorder.py` - Step 1 を考えていた時はうまく言語化できていなかったのだが、私の Step 1 の方法では、次の root をつけるために preorder を毎回スキャンする必要があった。 - この方法では、次に root になる preorder の位置を返して再帰間で共有できるため、余計な探索を省くことができていて、脳への収まりも良い。 + +# Step 3 + +## `step3_pick_next_root.py` + +次に root として使うべき preorder の位置を渡していく。 diff --git a/leetcode/105/step3.py b/leetcode/105/step3_pick_next_root.py similarity index 100% rename from leetcode/105/step3.py rename to leetcode/105/step3_pick_next_root.py From 67fd38bd3d5feaae3abad0cc478c0ea9b5924eda Mon Sep 17 00:00:00 2001 From: Kazuki Kijima Date: Tue, 17 Mar 2026 16:15:48 -0700 Subject: [PATCH 09/11] LeetCode 105: Add comment on span-based invariants --- leetcode/105/memo.md | 2 ++ leetcode/105/step2_span_based.py | 9 ++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/leetcode/105/memo.md b/leetcode/105/memo.md index fcd681a..53ed1c9 100644 --- a/leetcode/105/memo.md +++ b/leetcode/105/memo.md @@ -34,6 +34,8 @@ TODO: 時間計算量・空間計算量 # Step 3 +お二方の方法を何回か自分の感覚で書き直していくうちにしっくりくるようになった。 + ## `step3_pick_next_root.py` 次に root として使うべき preorder の位置を渡していく。 diff --git a/leetcode/105/step2_span_based.py b/leetcode/105/step2_span_based.py index e160c47..1cb4df2 100644 --- a/leetcode/105/step2_span_based.py +++ b/leetcode/105/step2_span_based.py @@ -25,7 +25,9 @@ def buildTree(self, preorder: list[int], inorder: list[int]) -> TreeNode | None: num_to_inorder_i = {num: i for i, num in enumerate(inorder)} def build_tree_helper( - preorder_span: Span, inorder_span: Span + # preorder_span and inorder_span represent the same subtree + preorder_span: Span, + inorder_span: Span, ) -> TreeNode | None: if preorder_span.is_empty(): return None @@ -33,15 +35,20 @@ def build_tree_helper( root_val = preorder[preorder_span.begin] root_inorder_i = num_to_inorder_i[root_val] + # When picking a root inorder index, the range is split into + # [left subtree | root | right subtree] inorder_left = Span(inorder_span.begin, root_inorder_i) inorder_right = Span(root_inorder_i + 1, inorder_span.end) + # [root | left subtree | right subtree] + # The size of left subtree is determined by inorder left size preorder_left = Span( preorder_span.begin + 1, preorder_span.begin + 1 + inorder_left.size ) preorder_right = Span( preorder_span.end - inorder_right.size, preorder_span.end ) + return TreeNode( val=root_val, left=build_tree_helper(preorder_left, inorder_left), From 75db1e26634ccb62f50b2b27da26b1ccc09b82a8 Mon Sep 17 00:00:00 2001 From: Kazuki Kijima Date: Tue, 17 Mar 2026 16:59:50 -0700 Subject: [PATCH 10/11] LeetCode 105: Add step 3 solution with span --- leetcode/105/memo.md | 12 ++++- leetcode/105/step3_represent_same_subtrees.py | 52 +++++++++++++++++++ 2 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 leetcode/105/step3_represent_same_subtrees.py diff --git a/leetcode/105/memo.md b/leetcode/105/memo.md index 53ed1c9..9bf498e 100644 --- a/leetcode/105/memo.md +++ b/leetcode/105/memo.md @@ -32,10 +32,18 @@ TODO: 時間計算量・空間計算量 - Step 1 を考えていた時はうまく言語化できていなかったのだが、私の Step 1 の方法では、次の root をつけるために preorder を毎回スキャンする必要があった。 - この方法では、次に root になる preorder の位置を返して再帰間で共有できるため、余計な探索を省くことができていて、脳への収まりも良い。 +両方の方法で、 +inorder の値 -> index の対応を持つ長さ n の hashmap をつくるの、また call stack も最悪 n 個積まれるので、空間計算量は O(n)。 +各ノードはちょうど一回だけ root として処理され、その処理も hashmap の使用によって O(1) に留まるので、全体の時間計算量は O(n)。 + # Step 3 -お二方の方法を何回か自分の感覚で書き直していくうちにしっくりくるようになった。 +お二方の方法を何回か自分の感覚で書き直していくうちにしっくりくるようになった。私の理解度が深まるにつれて、コードによる表現も結構変わったように思う。 ## `step3_pick_next_root.py` -次に root として使うべき preorder の位置を渡していく。 +次に root として使うべき preorder の位置を返しながら再帰的に処理していく。 + +## `step3_represent_same_subtrees.py` + +helper に渡される preorder / inorder の区間は同じ部分木を表しており、その区間を再帰的に分割していく。 diff --git a/leetcode/105/step3_represent_same_subtrees.py b/leetcode/105/step3_represent_same_subtrees.py new file mode 100644 index 0000000..5320a01 --- /dev/null +++ b/leetcode/105/step3_represent_same_subtrees.py @@ -0,0 +1,52 @@ +# 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 Span: + def __init__(self, begin: int, end: int) -> None: + self.begin = begin + self.end = end + + @property + def size(self) -> int: + return max(self.end - self.begin, 0) + + @property + def is_empty(self) -> bool: + return self.size == 0 + + +class Solution: + def buildTree(self, preorder: list[int], inorder: list[int]) -> TreeNode | None: + val_to_inorder_index = {val: index for index, val in enumerate(inorder)} + + def build_tree_helper( + preorder_span: Span, inorder_span: Span + ) -> TreeNode | None: + assert preorder_span.size == inorder_span.size + if preorder_span.is_empty: + return None + + root_preorder_index = preorder_span.begin + root_val = preorder[root_preorder_index] + root_inorder_index = val_to_inorder_index[root_val] + + inorder_left = Span(inorder_span.begin, root_inorder_index) + preorder_left = Span( + preorder_span.begin + 1, preorder_span.begin + 1 + inorder_left.size + ) + + inorder_right = Span(root_inorder_index + 1, inorder_span.end) + preorder_right = Span( + preorder_span.begin + 1 + preorder_left.size, preorder_span.end + ) + + return TreeNode( + val=root_val, + left=build_tree_helper(preorder_left, inorder_left), + right=build_tree_helper(preorder_right, inorder_right), + ) + + return build_tree_helper(Span(0, len(preorder)), Span(0, len(inorder))) From 1625da2dfe9ab74b727199825d0e55e09c47503f Mon Sep 17 00:00:00 2001 From: Kazuki Kijima Date: Tue, 17 Mar 2026 17:12:54 -0700 Subject: [PATCH 11/11] LeetCode 105: Add time/space complexity analysis --- leetcode/105/memo.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/leetcode/105/memo.md b/leetcode/105/memo.md index 9bf498e..83c5093 100644 --- a/leetcode/105/memo.md +++ b/leetcode/105/memo.md @@ -12,7 +12,8 @@ preorder で見つけた root を inorder 内で探すと、その位置より これを再帰的に当てはめていけば、binary tree が一意に決まる。 -TODO: 時間計算量・空間計算量 +call stack が最悪 O(n) で空間計算量は O(n)。 +各部分木ごとに線形探索を繰り返すため、時間計算量は O(n^2)。 未だしっくりこないし、この理解が正しいか自信はないが、他の方々のPRを見てみることにする。