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
135 changes: 135 additions & 0 deletions 102. Binary Tree Level Order Traversal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
### Step1

- 深さ順に探索できるといえばBFS
- dequeでBFSしているwhile文の中で、ついでにresultに突っ込む方法もありそう
- いただくコメントを予想すると、nodes_val_orderedとnodes_and_depthという変数名が分かりにくい可能性あり
- nodes_and_depthはwhileループの中で次に突っ込むやつだし、単数複数が統一されていないので、next_node_depth_pairsなどがいいかな
- nodes_val_orderedは、pairs_ordered_by_depthなどがいいかな

```python

from collections import deque

Choose a reason for hiding this comment

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

細かいですが、こちらは2行空けて、上の空行は不要な気がします。
https://peps.python.org/pep-0008/#blank-lines

class Solution:
def levelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
nodes_and_depth = deque()
nodes_and_depth.append((root, 0))

Choose a reason for hiding this comment

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

 nodes_and_depth = deque([(root, 0)])

でも良いですね。

nodes_val_ordered = []
while nodes_and_depth:
node, depth = nodes_and_depth.popleft()
if not node:
continue
nodes_val_ordered.append((node.val, depth))
nodes_and_depth.append((node.left, depth + 1))
nodes_and_depth.append((node.right, depth + 1))
result = []
prev_depth = None
for node, depth in nodes_val_ordered:
if depth != prev_depth:
same_depth_nodes = []
result.append(same_depth_nodes)
same_depth_nodes.append(node)
prev_depth = depth
return result
```

- 探索と同時に突っ込むのを考えて書いてみる
Copy link

Choose a reason for hiding this comment

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

同時に突っ込む方法は、上の形をもう少し形式的に変形することで書けませんか。

上のブロックで、nodes_val_ordered に append していて、下のブロックで nodes_val_ordered を for で回しているのだから、中身をappend のところに書けば理屈上動きそうです。

- これで考えると、popleft()をする必要がないのでlistでいいね
- currentはnextとの比較なので、今回は変数名として許容

```python

class Solution:
def levelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
current_depth_nodes = [root]
result = []
while current_depth_nodes:
next_depth_nodes = []
current_depth_nodes_val = []
for node in current_depth_nodes:
if not node:
continue
current_depth_nodes_val.append(node.val)
next_depth_nodes.append(node.left)
next_depth_nodes.append(node.right)
if current_depth_nodes_val:
result.append(current_depth_nodes_val)
current_depth_nodes = next_depth_nodes
return result
```

## Step2

- 参照
- https://github.com/sakupan102/arai60-practice/pull/27/files
- node.depthが同じものを入れるときの処理の選択肢。appendが2回に分かれるのはいまいち(Step1の上のやつもそれを考えて書いた)
- https://discord.com/channels/1084280443945353267/1196472827457589338/1196472926862577745 の22
- 今回の場合node.leftを入れるタイミングでNoneをチェックしてもよかったか
- https://github.com/hayashi-ay/leetcode/pull/32/files
- 再帰でもかけるのか
- 最初に拡張するときにwhileの方が読むやすいのは確かに
- 再帰でも書いてみる
- depthを持ってるので、突っ込むときにresult[depth]にアクセスすれば、nodesの順番を整えなくてもOK。なぜか気が付かなかった。
- depth ≥ len(result)の方が好きという意見もあった(不等号の向き)。

```python
class Solution:
def levelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
def append_nodes_val_to_result(node, depth):
if not node:
return
while len(result) <= depth:
result.append([])
result[depth].append(node.val)
append_nodes_val_to_result(node.left, depth + 1)
append_nodes_val_to_result(node.right, depth + 1)

result = []
append_nodes_val_to_result(root, 0)
return result
```

- currentとnextの両方を持つことはせず、配列の要素数でlevelを管理
Copy link

Choose a reason for hiding this comment

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

シンプルに level を持たせるほうが可読性と保守性が高いかと思いました。コード全体を読めば一番最後のリストに要素を追加すればよいことは自明かもですが、もう少しコードの規模が大きくなると急に理解が難しくなりかつバグが発生しやすくなる気がします。result[level].append(node.val) の書き方だとこのコード単体でも意味がわかりやすいです。

Copy link
Owner Author

Choose a reason for hiding this comment

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

ありがとうございます。
TORUS0818/leetcode#28 (comment)
こういう意見もあるんですよね…何が理解のボトルネックになるのかというのが人によって違いそうで難しいです。
ただ、上の例だとlevel_ordered_valuesという変数名でlevel順というのがすぐわかるのに対して今回はすぐはわからないので、そういう違いもあるのかもしれません。


```python
class Solution:
def levelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
result = []
if not root:
return []
nodes = deque([root])
while nodes:
result.append([])
for _ in range(len(nodes)):
node = nodes.popleft()
result[-1].append(node.val)
if node.left:
nodes.append(node.left)
if node.right:
nodes.append(node.right)
return result
```

## Step3

```python

from collections import deque

class Solution:
def levelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
if not root:
return []
nodes = deque([root])
result = []
while nodes:
result.append([])
for _ in range(len(nodes)):
Copy link

Choose a reason for hiding this comment

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

range(len(nodes)) でループを回している中に nodes.popleft()node.append() のように nodes に変更を与えるような処理があるのがわかりにくいと感じました。このケースでは L.127 に到達した時点で len(nodes) が評価されるので、その後ループ内で nodes の長さを変更させてもループの回数は変わらないと思います。(自分で簡単なコードで試してみました)

ただ for node in nodes のようなコードの場合は、nodes の長さを変えたりするとその影響がループにも及びます。(こちらも試してみました)

読んでいる際に「これはループの回数が変化するのかな?」という疑問を持ったので、読み手としては理解が難しい(可読性がよくない)コードなのではと感じました。

Copy link
Owner Author

Choose a reason for hiding this comment

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

これは確かにで、len(nodes)の評価はfor文回す前の最初にされるのは、自分も試してへ〜となる感じで、なんとなくで書いてしまってました。
これがPythonプログラマだったらすぐわかるとかだったらいいですが、そうでもないっぽいので別変数で用意した方がいいかもですね。

node = nodes.popleft()
result[-1].append(node.val)
if node.left:
nodes.append(node.left)
if node.right:
nodes.append(node.right)
return result
```