Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions leetcode/105/memo.md
Original file line number Diff line number Diff line change
@@ -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 の区間は同じ部分木を表しており、その区間を再帰的に分割していく。
24 changes: 24 additions & 0 deletions leetcode/105/step1.py
Original file line number Diff line number Diff line change
@@ -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))
31 changes: 31 additions & 0 deletions leetcode/105/step2_pick_preorder.py
Original file line number Diff line number Diff line change
@@ -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]
58 changes: 58 additions & 0 deletions leetcode/105/step2_span_based.py
Original file line number Diff line number Diff line change
@@ -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)))
31 changes: 31 additions & 0 deletions leetcode/105/step3_pick_next_root.py
Original file line number Diff line number Diff line change
@@ -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]
52 changes: 52 additions & 0 deletions leetcode/105/step3_represent_same_subtrees.py
Original file line number Diff line number Diff line change
@@ -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)))