Skip to content

Add 617. Merge Two Binary Trees.md #23

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
199 changes: 199 additions & 0 deletions 617. Merge Two Binary Trees.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
# step 1

再帰する解法。ノードの数が最大2000とあり、デフォルトのrecursion limit(1000)は超える。inner functionが多くなった。処理は簡単なので許容範囲と判断した。

root1, root2の深さの大きい方の数だけ再帰する。それぞれのノードの数をm, nとすると、
- time complexity: O(m + n)
- space complexity: O(m + n)
```python
class Solution:
def mergeTrees(self, root1: Optional[TreeNode], root2: Optional[TreeNode]) -> Optional[TreeNode]:
def get_val(node: Optional[TreeNode]) -> int:
if node is None:
return 0
return node.val
def get_left(node: Optional[TreeNode]) -> Optional[TreeNode]:
if node is None:
return None
return node.left
def get_right(node: Optional[TreeNode]) -> Optional[TreeNode]:
if node is None:
return None
return node.right

if root1 is None and root2 is None:
return None

merged_val = get_val(root1) + get_val(root2)
merged_root = TreeNode(val=merged_val)
merged_root.left = self.mergeTrees(get_left(root1), get_left(root2))
merged_root.right = self.mergeTrees(
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

一貫性があると読みやすくなるので、改行するしないをleftと合わせるほうが良いと思いました。

get_right(root1),
get_right(root2)
)
return merged_root
```

非再帰DFSを使う方法はバックトラックをどうすればいいかわからなかった。

# step 2
- https://github.com/olsen-blue/Arai60/pull/23/files
- 片方がNoneになったとき、もう片方をそのままmergeしたtreeに繋げていた。
- 非破壊だが、一部のオブジェクトを共有している
- コピーするなり、そのときNoneとなったノードをTreeNode(0)で上書きするなりしていた。
- https://github.com/olsen-blue/Arai60/pull/23/files#r1929995500
- https://github.com/tarinaihitori/leetcode/pull/23/files
- BFSによる解法。ノードとそれらをマージしたノードを持っていた。
- https://github.com/tarinaihitori/leetcode/pull/23/files#r1919824481
- 冗長さを排除した書き方。
- pythonでもまだまだ知らない書き方があるなと感じた。
- https://github.com/hroc135/leetcode/pull/22/files#r1826509019
- stackに入りうるサイズの概算
- > 事前に確保する配列の大きさをどの値にするのが有効かは実際に入力ノードの形状と大きさに依存するので、事前に最適なサイズを決定するのは難しいと思います
このサイズが性能にクリティカルな影響を与えるわけではないですが、ハードコードよりも環境変数などで外部から注入できるようにしたほうが良いと思いました
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

私は、この確保はあんまり賛成しません。

「配列を事前に確保しておくことで速くなるメリット」「事前に確保するコードがあることで悪い影響が起きる可能性」を考えて、前者がほとんどないと判断します。

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

「事前に確保するコードがあることで悪い影響が起きる可能性」というのは、

  • 確保したけれど使わなかったオーバーヘッド
  • 解放時の手間

とかでしょうか?
後者はともかく、前者はlinuxに関しては、アクセスがあるまで仮想アドレスが割り当てられるだけで実際に物理メモリが割り当てられるわけではないからそれほど害はないと感じています。
考慮しきれていないものがあったら教えてください。


DFS
- time complexity: O(m + n)
- space complexity: O(log(m + n))
```python
class Solution:
def mergeTrees(self, root1: Optional[TreeNode], root2: Optional[TreeNode]) -> Optional[TreeNode]:
def get_val(node: Optional[TreeNode]) -> int:
if node is None:
return 0
return node.val
Comment on lines +61 to +64
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

このあたりのアクセッサーが冗長かなと思います。ここらへん減らしたかったら、番兵を立てるのも手です。

https://docs.google.com/document/d/11HV35ADPo9QxJOpJQ24FcZvtvioli770WWdZZDaLOfg/edit?tab=t.0#heading=h.cxy3cik6kyqx

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

これならば、root1, 2がNoneかどうかで4通りすべて書く必要がなくなりますね。ありがとうございます。

def get_left(node: Optional[TreeNode]) -> Optional[TreeNode]:
if node is None:
return None
return node.left
def get_right(node: Optional[TreeNode]) -> Optional[TreeNode]:
Copy link

@colorbox colorbox Mar 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nodeがnoneだったらnoneを返して、そうでなければleft/rightを返す、これってメソッドの呼び出し元でnode.left/node.rightを呼び出すのと何も違いがなく見えます。
このメソッドはなくしてしまったほうが読みやすくなるように思えます。
すみません、読み間違えました。
ただ、メソッドをなくしたほうがよみやすそうという感覚は変わらずでして、nodeの存在確認は
if node1_left is not None or node2_left is not None:
の行でやってしまって良いと思いました。

if node is None:
return None
return node.right

if root1 is None and root2 is None:
return None

merged_val = get_val(root1) + get_val(root2)
merged_root = TreeNode(val=merged_val)
stack = [(root1, root2, merged_root)]
while stack:
node1, node2, merged_node = stack.pop()

node1_left = get_left(node1)
node2_left = get_left(node2)
if node1_left is not None or node2_left is not None:
left_val = get_val(node1_left) + get_val(node2_left)
merged_node.left = TreeNode(val=left_val)
stack.append((node1_left, node2_left, merged_node.left))

node1_right = get_right(node1)
node2_right = get_right(node2)
if node1_right is not None or node2_right is not None:
right_val = get_val(node1_right) + get_val(node2_right)
merged_node.right = TreeNode(val=right_val)
stack.append((node1_right, node2_right, merged_node.right))
return merged_root
```

BFS
- time complexity: O(m + n)
- space complexity: O(log(m + n))
```python
class Solution:
def mergeTrees(self, root1: Optional[TreeNode], root2: Optional[TreeNode]) -> Optional[TreeNode]:
def get_val(node: Optional[TreeNode]) -> int:
if node is None:
return 0
return node.val
def get_left(node: Optional[TreeNode]) -> Optional[TreeNode]:
if node is None:
return None
return node.left
def get_right(node: Optional[TreeNode]) -> Optional[TreeNode]:
if node is None:
return None
return node.right

if root1 is None and root2 is None:
return None

merged_val = get_val(root1) + get_val(root2)
merged_root = TreeNode(val=merged_val)
stack = [(root1, root2, merged_root)]
while stack:
node1, node2, merged_node = stack.pop()

node1_left = get_left(node1)
node2_left = get_left(node2)
if node1_left is None and node2_left is None:
merged_node.left = None
else:
left_val = get_val(node1_left) + get_val(node2_left)
merged_node.left = TreeNode(val=left_val)
stack.append((node1_left, node2_left, merged_node.left))

node1_right = get_right(node1)
node2_right = get_right(node2)
if node1_right is None and node2_right is None:
merged_node.right = None
else:
right_val = get_val(node1_right) + get_val(node2_right)
merged_node.right = TreeNode(val=right_val)
stack.append((node1_right, node2_right, merged_node.right))
return merged_root
```

もう少しコンパクトに書いたもの(https://github.com/tarinaihitori/leetcode/pull/23/files#r1919824481 の写経)
- time complexity: O(m + n)
- space complexity: O(log(m + n))
```python
class Solution:
def mergeTrees(self, root1: Optional[TreeNode], root2: Optional[TreeNode]) -> Optional[TreeNode]:
roots = list(filter(None, [root1, root2]))
if not roots:
return None
root = TreeNode()
stack = [(root, roots)]
while stack:
dst_node, src_nodes = stack.pop()
Copy link

@olsen-blue olsen-blue Mar 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

filterは初めてみましたが、None除去したりは便利だと感じました。
https://docs.python.org/3/library/functions.html#filter

dst は、 src と対比されていそうですが、これから作りたい destination node みたいなイメージですかね。

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

そうなりますね(完全にodaさんのものの写経になりますが)。

ループの先頭時点で、dst_nodeはTreeNode()となっているが値や子ノードが適切に設定されていない、src_nodesはdst_nodeと位置的に対応するnodeとなっています。ループないでvalやleft, rightを決めていくことになります

dst_node.val = sum(n.val for n in src_nodes)

lefts = [n.left for n in src_nodes if n.left]
if lefts:
dst_node.left = TreeNode()
stack.append((dst_node.left, lefts))

rights = [n.right for n in src_nodes if n.right]
if rights:
dst_node.right = TreeNode()
stack.append((dst_node.right, rights))
return root
```

# step 3

```python
class Solution:
def mergeTrees(self, root1: Optional[TreeNode], root2: Optional[TreeNode]) -> Optional[TreeNode]:
def get_val(node: Optional[TreeNode]) -> int:
if node is None:
return 0
return node.val
def get_left(node: Optional[TreeNode]) -> Optional[TreeNode]:
if node is None:
return None
return node.left
def get_right(node: Optional[TreeNode]) -> Optional[TreeNode]:
if node is None:
return None
return node.right

if root1 is None and root2 is None:
return None
merged_val = get_val(root1) + get_val(root2)
merged_root = TreeNode(val=merged_val)
merged_root.left = self.mergeTrees(get_left(root1), get_left(root2))
merged_root.right = self.mergeTrees(get_right(root1), get_right(root2))
return merged_root
```