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
39 changes: 39 additions & 0 deletions 102/bench_list_vs_tuple.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
from __future__ import annotations

import sys
import timeit


def main() -> None:
n = 200000
number = 200
repeat = 7

list_data = list(range(n))
tuple_data = tuple(list_data)

def loop_list() -> int:
s = 0
for x in list_data:
s += x
return s

def loop_tuple() -> int:
s = 0
for x in tuple_data:
s += x
return s

assert loop_list() == loop_tuple()

t_list = min(timeit.repeat(loop_list, number=number, repeat=repeat))
t_tuple = min(timeit.repeat(loop_tuple, number=number, repeat=repeat))

print(f"n={n} number={number} repeat={repeat} (best of repeat)")
print(f"list : {t_list:.6f} s")
print(f"tuple: {t_tuple:.6f} s")
print(f"tuple/list: {t_tuple / t_list:.4f}x")


if __name__ == "__main__":
main()
44 changes: 44 additions & 0 deletions 102/memo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# 102. Binary Tree Level Order Traversal
[リンク](https://leetcode.com/problems/binary-tree-level-order-traversal/description/)

- sol1.py: 再帰でかいたがロジックがやや冗長かもしれない
- DFSで帰りがけ順にマージする
- 木が偏っている場合、時間計算量O(n**2)
- 空間計算量 O(n)+O(h)

- https://github.com/mamo3gr/arai60/blob/102_binary-tree-level-order-traversal/102_binary-tree-level-order-traversal/step2.py
- 行きがけ順の再帰
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

これ、素直に対応するループに直せますか。再帰呼ぶかわりにスタックにやることを積んで dfs にするということです。(再帰とループの変形をすること自体が選択として見えているかが気にかかりました。)

再帰は制限が多いので。
https://docs.google.com/document/d/11HV35ADPo9QxJOpJQ24FcZvtvioli770WWdZZDaLOfg/edit?tab=t.0#heading=h.uvguf4c3q02d
再帰をループに
https://docs.google.com/document/d/11HV35ADPo9QxJOpJQ24FcZvtvioli770WWdZZDaLOfg/edit?tab=t.0#heading=h.deivkzaqvetb

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.

新しく書きました。pythonのsys.setrecursionlimitなど知らなかったので勉強になりました。
https://docs.python.org/3/library/sys.html#sys.setrecursionlimit

- 似た感じで書く(sol2.py)
- 時間計算量O(n**2)
- 空間計算量 O(n)+O(h)

- https://discord.com/channels/1084280443945353267/1192728121644945439/1194203372115464272
- if elseで例外処理を行うより、ifで例外だけを処理した方が良い
> 「機械の使い方の説明です。まず、青いランプが5つついていることを確認してください。ついている場合、…使い方の説明…。ランプがついていなかった場合は、直ちに使用を中止して事務所に連絡してください。…機械の使い方の続き…。」

- queueを使った BFS (sol3.py)
- 時間計算量 O(n)
- 空間計算量 O(n)

- 再帰には深さに限界があるなど制約が多いので、ループに書き直せるようにしておく
- https://docs.google.com/document/d/11HV35ADPo9QxJOpJQ24FcZvtvioli770WWdZZDaLOfg/edit?tab=t.0#heading=h.deivkzaqvetb
- スタックをループに直すのは頻繁に必要になる。
- 再帰だとログが難しい
- spl2_loop.py:再帰+dfs
> 例えばですけれども、1万回くらい呼ぶと1回くらいおかしな動きをする機能があって、乱数などが絡んでいるから再現も難しいので、ある特定の if 文を通ったときにログを出力したいとします。再帰で書いていると、そもそもどういう状況でそこに到達したのかの関数の呼び出し元の情報などを出力するのが大変です。
- https://github.com/irohafternoon/LeetCode/pull/6#discussion_r2019026748

- Java デフォルトが 1M のスタックサイズ、C は 10M くらいが普通。
- Python setrecursionlimit: 言語処理系が設ける再帰上限
- クラッシュ(Cスタック破壊)を避けるために、先に例外で止める
- https://docs.python.org/3/library/sys.html#sys.getrecursionlimit
- https://docs.python.org/3/library/sys.html#sys.setrecursionlimit
- 言語処理形が上限を設ける
- クイックソートで長い方を末尾再帰最適化するのもスタックオーバーフローを防ぐため
- https://nuc.hatenadiary.org/entry/2021/03/31#:~:text=%E7%9F%AD%E3%81%84%E6%96%B9%E3%81%8B%E3%82%89%E5%86%8D%E5%B8%B0%E3%81%97%E3%81%A6%E3%80%81%E9%95%B7%E3%81%84%E6%96%B9%E3%81%AF%E6%9C%AB%E5%B0%BE%E5%86%8D%E5%B8%B0%E6%9C%80%E9%81%A9%E5%8C%96%E3%81%99%E3%82%8B%E3%81%93%E3%81%A8

- tupleとlistのベンチマーク: tupleの方がややはやい
n=200000 number=200 repeat=7 (best of repeat)
list : 1.694295 s
tuple: 1.462984 s
tuple/list: 0.8635x
21 changes: 21 additions & 0 deletions 102/sol1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from itertools import zip_longest


# 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:
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

ロジックが把握しにくく感じました。自分に関数型言語の素養があまりないためかもしれません。

def levelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
if root is None:
return []
left_level_order = self.levelOrder(root.left)
right_level_order = self.levelOrder(root.right)
merged_level_order = [[root.val]]
for left, right in zip_longest(
left_level_order, right_level_order, fillvalue=[]
):
merged_level_order.append(left + right)
return merged_level_order
25 changes: 25 additions & 0 deletions 102/sol2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# 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 levelOrder(self, root: Optional["TreeNode"]) -> List[List[int]]:
level_order = []

def append_value_by_level(node, level):
if node is None:
return

if level == len(level_order):
level_order.append([])
level_order[level].append(node.val)

append_value_by_level(node.left, level + 1)
append_value_by_level(node.right, level + 1)

append_value_by_level(root, 0)
return level_order
26 changes: 26 additions & 0 deletions 102/sol2_loop.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from collections import deque

# 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 levelOrder(self, root: Optional["TreeNode"]) -> List[List[int]]:
if root is None:
return []
level_order = []
node_to_traverse = deque([(0, root)])

while node_to_traverse:
level, node = node_to_traverse.pop()
for child in (node.right, node.left):
if child is not None:
node_to_traverse.append((level + 1, child))
while len(level_order) <= level:
level_order.append([])
level_order[level].append(node.val)
return level_order
27 changes: 27 additions & 0 deletions 102/sol3.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from collections import deque

# 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 levelOrder(self, root: Optional["TreeNode"]) -> List[List[int]]:
level_order = []
if root is None:
return level_order
queue = deque([root])
while queue:
values_by_level = []
level_size = len(queue)
for _ in range(level_size):
Comment on lines +19 to +20
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

キューにはなるべく処理に必要な情報がセットで入っていてほしいと思います(あくまで私の場合)。したがって、キュー外での管理になっている level_size はタプルにしてキューに詰めるか、next_level_nodes: list[TreeNode] に次の階層のnodeをappendしてqueueに差し替える、という実装の方が好みです。

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.

なるほど、そのような実装もあるのですね。こちらを好む理由も理解しました。
今回は現状の実装を採用しようと思います。

node = queue.popleft()
values_by_level.append(node.val)
for child in (node.left, node.right):
if child is not None:
queue.append(child)
level_order.append(values_by_level)
return level_order