diff --git a/leetcode/105/memo.md b/leetcode/105/memo.md new file mode 100644 index 0000000..83c5093 --- /dev/null +++ b/leetcode/105/memo.md @@ -0,0 +1,50 @@ +# Step 1 + +先日、レビュー依頼を受けて取り組んだのだが、初見ではいくら例をこねくり回してもさっぱりわからなかった (アルゴリズムに落とし込めなかった)。 +前回取り組んだ際の私の理解では、 + +preorder では、左右の部分木より先に root が現れる。 +inorder では、左・root・右 の順序が保たれる。 + +これを組み合わせて + +preorder で見つけた root を inorder 内で探すと、その位置より左にある値は左の部分木、右にある値は右の部分木に属する。 + +これを再帰的に当てはめていけば、binary tree が一意に決まる。 + +call stack が最悪 O(n) で空間計算量は O(n)。 +各部分木ごとに線形探索を繰り返すため、時間計算量は O(n^2)。 + +未だしっくりこないし、この理解が正しいか自信はないが、他の方々の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..." +- 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 の位置を返して再帰間で共有できるため、余計な探索を省くことができていて、脳への収まりも良い。 + +両方の方法で、 +inorder の値 -> index の対応を持つ長さ n の hashmap をつくるの、また call stack も最悪 n 個積まれるので、空間計算量は O(n)。 +各ノードはちょうど一回だけ root として処理され、その処理も hashmap の使用によって O(1) に留まるので、全体の時間計算量は O(n)。 + +# Step 3 + +お二方の方法を何回か自分の感覚で書き直していくうちにしっくりくるようになった。私の理解度が深まるにつれて、コードによる表現も結構変わったように思う。 + +## `step3_pick_next_root.py` + +次に root として使うべき preorder の位置を返しながら再帰的に処理していく。 + +## `step3_represent_same_subtrees.py` + +helper に渡される preorder / inorder の区間は同じ部分木を表しており、その区間を再帰的に分割していく。 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)) 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] diff --git a/leetcode/105/step2_span_based.py b/leetcode/105/step2_span_based.py new file mode 100644 index 0000000..1cb4df2 --- /dev/null +++ b/leetcode/105/step2_span_based.py @@ -0,0 +1,58 @@ +# 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 and inorder_span represent the same subtree + 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] + + # 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), + right=build_tree_helper(preorder_right, inorder_right), + ) + + return build_tree_helper(Span(0, len(preorder)), Span(0, len(inorder))) diff --git a/leetcode/105/step3_pick_next_root.py b/leetcode/105/step3_pick_next_root.py new file mode 100644 index 0000000..bac9eca --- /dev/null +++ b/leetcode/105/step3_pick_next_root.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]) -> TreeNode | None: + 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 + ) -> tuple[TreeNode | None, int]: + if inorder_end <= inorder_begin: + return None, root_preorder_index + + root_val = preorder[root_preorder_index] + root_inorder_index = val_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(root_val, left, right), next_root_preorder_index + + return build_tree_helper(0, 0, len(inorder))[0] 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)))