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
43 changes: 43 additions & 0 deletions 617-Merge-Two-Binary-Trees/note.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# 617. Merge Two Binary Trees

https://leetcode.com/problems/merge-two-binary-trees/description/

## step1(まず通す)

再帰で書くのがぱっと見わかりやすそうだったのでまずはそれで書く。再帰でスタックする数も高々木の高さなので問題ないはず。

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

再帰でスタックする数も高々木の高さなので問題ないはず。

これくらい書いておくと,わかっている感は伝わっていいですね.

pythonの再帰の上限について簡単に調べてみたのですが,default recursion limitは1000らしいです.(詳しくないので頓珍漢なこと言ってたらすみません)
https://peps.python.org/pep-0651/
なので,0 <= N <= 2000 である今回の問題では,一概に問題ないとは言えないかもしれません.

自分のローカルで試したのが以下

➜  ~ python
Python 3.14.5 (main, May 10 2026, 10:21:34) [Clang 21.0.0 (clang-2100.0.123.102)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys.getrecursionlimit()
1000
>>>

leetcodeで同様にすると550000 とでました.引き上げられているのかもしれませんね.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

ありがとうございます。デフォルトのリミットは 1000 ですね。Python で再起が好まれない理由の一つだと思います。

与えられた木が一本道で 2000 ノードあるみたいな状態だとスタックに積まれる数も2000になるのでデフォルトだと溢れる可能性ありますね。見積もり不正確でした。ご指摘ありがとうございます。

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

再帰でスタックする数

日本語として不自然に感じました。スタックは stack と stuck の英単語があり、どちらもソフトウェアエンジニアリングの文脈で使われると思います。スタックすると日本語で書いた場合は、後者を指すことが多いように思います。後者は、止まる、行き詰まるという意味です。
再帰呼び出しの回数、もしくは、再帰でスタックメモリに積まれるスタックフレームの数、と書くと、正確だと思います。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

ありがとうございます。

再帰呼び出しの回数、もしくは、再帰でスタックメモリに積まれるスタックフレームの数、と書くと、正確だと思います

このような正確な表現を心がけます。


## step2(整形&他の人のコードを読む)

場合によって新しいオブジェクトが作られたり入力が再利用されたりしていて統一感のないコードになってしまった。挙動が一貫していないのは利用者からすると困ると思うので、両極端で書いてみる。

入力をインプレースで変更するパターン: step2-1.py

全ノード再構築するパターン: step2-2.py

書いてみたが、個人的には一番書きやすく感じられるのは step1.py。そもそも入力が破壊される可能性があるなら気をつけないといけないのは一緒だと思うと、step2-1.py よりは step1.py のほうが良さそうか。

とはいえ、自分が使う立場になって一番嬉しいのは step2-2.py だと思う。

### 他の人はどう書いているか

コメント集を見ても、入力を破壊するか否かは重要な論点っぽい。パッと書いてすぐそこを気にすることができたので思考回路としては割と良さそう。

- https://github.com/shintaro1993/arai60/pull/27/changes
- 片方が None のときに入力を破壊しないためのやり方にもいくつかあって
- (step2-2.py で私がやっているように)新しく作るノードの子を再帰的に計算する時に、引数の片方を None にする
- 番兵を使う = None を 「値が 0 で、左右の子が None」のノードに置き換えてから root1 も root2 も None でないときと同じ処理をする
- 本質的にはにはどちらも同じことをやっているが、見やすさでいうと後者かな
- 前者の方が宣言的で好きではある
- https://github.com/Fuminiton/LeetCode/pull/23/changes#r1997660222
- 「一部のノードを共有すると、他の破壊的なメソッドとの相性で残念なことになるかもしれませんね。」
- step1.py や step2-1.py は単に入力を破壊しているというだけでなくて一部ノードが tree1 と tree2 で共有されてしまう、というのはあまり明示的に意識してなかった。

## step3(10分以内にさっとかける \* 3回)

破壊しないで書く。

## step4

再帰→スタックの変換。

tree1, tree2 の left, right をスタックに詰めた時点で merged_node もデフォルトの TreeNode を受け皿として一緒に詰めておかないと親と子を紐づけられない、というのを理解するのにちょっと時間がかかった。
28 changes: 28 additions & 0 deletions 617-Merge-Two-Binary-Trees/step1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from typing import Optional


class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right


class Solution:
def mergeTrees(
self, root1: Optional[TreeNode], root2: Optional[TreeNode]
) -> Optional[TreeNode]:
if root1 is None and root2 is None:
return None

if root1 is None:
return root2

if root2 is None:
return root1

return TreeNode(
val=root1.val + root2.val,
left=self.mergeTrees(root1.left, root2.left),
right=self.mergeTrees(root1.right, root2.right),
)
27 changes: 27 additions & 0 deletions 617-Merge-Two-Binary-Trees/step2-1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from typing import Optional


class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right


class Solution:
def mergeTrees(
self, root1: Optional[TreeNode], root2: Optional[TreeNode]
) -> Optional[TreeNode]:
if root1 is None and root2 is None:
return None

if root1 is None:
return root2

if root2 is None:
return root1

root1.val += root2.val
root1.left = self.mergeTrees(root1.left, root2.left)
root1.right = self.mergeTrees(root1.right, root2.right)
return root1
36 changes: 36 additions & 0 deletions 617-Merge-Two-Binary-Trees/step2-2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
from typing import Optional


class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right


class Solution:
def mergeTrees(
self, root1: Optional[TreeNode], root2: Optional[TreeNode]
) -> Optional[TreeNode]:
if root1 is None and root2 is None:
return None

if root1 is None:
return TreeNode(
val=root2.val,
left=self.mergeTrees(None, root2.left),
right=self.mergeTrees(None, root2.right),
)

if root2 is None:
return TreeNode(
val=root1.val,
left=self.mergeTrees(root1.left, None),
right=self.mergeTrees(root1.right, None),
)

return TreeNode(
val=root1.val + root2.val,
left=self.mergeTrees(root1.left, root2.left),
right=self.mergeTrees(root1.right, root2.right),
)
27 changes: 27 additions & 0 deletions 617-Merge-Two-Binary-Trees/step3.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from typing import Optional


class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right


class Solution:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

再帰は明示的なstackを用いてiterativeに書き直せるので、それも書いておくといいと思います。
現実的にもデータをスタック領域から避けstack overflowを防止できるという点で重要です。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

ありがとうございます。書いてみます。

def mergeTrees(
self, root1: Optional[TreeNode], root2: Optional[TreeNode]
) -> Optional[TreeNode]:
if root1 is None and root2 is None:
return None

if root1 is None:
root1 = TreeNode(0)
if root2 is None:
root2 = TreeNode(0)

return TreeNode(
val=root1.val + root2.val,
left=self.mergeTrees(root1.left, root2.left),
right=self.mergeTrees(root1.right, root2.right),
)
35 changes: 35 additions & 0 deletions 617-Merge-Two-Binary-Trees/step4.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from typing import Optional


class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right


class Solution:
def mergeTrees(
self, root1: Optional[TreeNode], root2: Optional[TreeNode]
) -> Optional[TreeNode]:
if root1 is None and root2 is None:
return None

merged_root = TreeNode()
stack = [(root1, root2, merged_root)]
while stack:
node1, node2, merged_node = stack.pop()
if node1 is None:
node1 = TreeNode(0)
if node2 is None:
node2 = TreeNode(0)

merged_node.val = node1.val + node2.val
if not (node1.left is None and node2.left is None):
merged_node.left = TreeNode()
stack.append((node1.left, node2.left, merged_node.left))
if not (node1.right is None and node2.right is None):
merged_node.right = TreeNode()
stack.append((node1.right, node2.right, merged_node.right))

return merged_root