Skip to content
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
106 changes: 106 additions & 0 deletions 617. Merge Two Binary Trees.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
1st
再帰が思いついたので、再帰で書く。
時間計算量:O(n)
空間計算量:O(n) バランスしている木の場合は O(logn)

```python
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

new_tree = TreeNode(root1.val + root2.val)

new_tree.left = self.mergeTrees(root1.left, root2.left)
new_tree.right = self.mergeTrees(root1.right, root2.right)
return new_tree
```

2nd
新しくツリーを作るのではなく、一つのツリーに重ねる方法にする。入力を破壊しても良い場合はこれでもよさそう。あとメモリ使用量も若干効率がいい。
個人的にはイミュータブルのほうが安全で嬉しいので、新しくツリーを作るほうを選ぶ。
`if root1 is None and root2 is None`はなくてもよいことに気づいたので、削除した。

```python
class Solution:
def mergeTrees(self, root1: Optional[TreeNode], root2: Optional[TreeNode]) -> Optional[TreeNode]:
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

```

BFS で書いたが、左右両方に子がある場合、左側に子がある場合など条件が複雑になった。
スタックオーバーフローは考慮しなければならないが、node の数が大きくない場合は再帰で書きたい。

```python
class Solution:
def mergeTrees(self, root1: TreeNode, root2: TreeNode) -> TreeNode:
if not root1:
return root2
if not root2:
return root1

merged_root = TreeNode(root1.val + root2.val)

queue = deque([(root1, root2, merged_root)])

while queue:
node1, node2, merged_node = queue.popleft()
if node1.left and node2.left:
merged_left = TreeNode(node1.left.val + node2.left.val)
merged_node.left = merged_left
queue.append((node1.left, node2.left, merged_left))
elif node1.left:
merged_node.left = node1.left
elif node2.left:
merged_node.left = node2.left

if node1.right and node2.right:
merged_right = TreeNode(node1.right.val + node2.right.val)
merged_node.right = merged_right
queue.append((node1.right, node2.right, merged_right))
elif node1.right:
merged_node.right = node1.right
elif node2.right:
merged_node.right = node2.right

return merged_root
```
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 は、メンバ変数へのポインターが持てないので、どうしても繰り返し感がでますね。下のような方法は一応考えてみました。

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()
            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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

これも別に良いコードではないですが、C++ だとどこに書き込むかをスタックに積むことができるので、少し簡単になるのです。それを擬似的に Python で表現してみました。

from functools import partial

class Solution:
    def mergeTrees(self, root1: Optional[TreeNode], root2: Optional[TreeNode]) -> Optional[TreeNode]:
        dummy = TreeNode()
        stack = [(partial(dummy.__setattr__, "left"), [root1, root2])]
        while stack:
            write_node, nodes = stack.pop()
            nodes = list(filter(None, nodes))
            if not nodes:
                continue
            val = sum(n.val for n in nodes)
            output = TreeNode(val)
            write_node(output)
            for child in ['left', 'right']:
                stack.append((partial(output.__setattr__, child), [n.__getattribute__(child) for n in nodes]))
        return dummy.left

Copy link
Copy Markdown

Choose a reason for hiding this comment

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


過去ログを見る。
https://github.com/fhiyo/leetcode/pull/25

3rd
再帰の方法を選んだ。
理由は、
もしライブラリーを使う人が、
「この関数は、root1 をただ読むだけだろう」と思い込んでいて、「マージの結果として新しいツリーを返すだけだろう」と予想している状態で、実際は root1 をガンガン上書きする関数が呼ばれたら、
後から root1 を再利用しようとしたときに予期せぬ値や構造になっていて「えっ、なんでツリーが書き換わってるの?」と驚くことになりそうだと思った。
もし破壊的に書くなら、
ライブラリのドキュメントに「破壊的です」と書くかな。
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

全く新しい木を構築していく方法もあると思います。step3のコードでも下記のようなケースにroot1とmergeTreesの返り値でポインタの共有部分があるとroot1への変更が返り値にも影響してしまう場合があるのは気をつけないといけないと思いました。pythonのポインタについて誤解していたらごめんなさい

root1 = TreeNode(1)
root2 = None
merged_tree = mergeTrees(root1, root2)
print(merged_tree.val)  # 1
root1.val += 1
print(merged_tree.val)  # 2

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Ruby では ! をつけることによって表現する流儀がありますね。Python では変数名に使えないですが。
https://www.ruby-lang.org/en/documentation/faq/7/#destructive-metho


```python
class Solution:
def mergeTrees(self, root1: TreeNode, root2: TreeNode) -> TreeNode:
if root1 is None:
return root2
if root2 is None:
return root1
Comment on lines +96 to +99
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Step 3の方法は、入力を破壊はしていないものの、マージ後の木と入力の木でノードが共有される可能性があります。


new_node = TreeNode(root1.val + root2.val)
new_node.left = self.mergeTrees(root1.left, root2.left)
new_node.right = self.mergeTrees(root1.right, root2.right)

return new_node
```