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
148 changes: 148 additions & 0 deletions 102_BinaryTreeLevelOrderTraversal/solution.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
# 問題
https://leetcode.com/problems/binary-tree-level-order-traversal/

二分木の root を受け取り、ノードの値を「レベル順(上から下へ、各レベルは左から右へ)」に、レベルごとの配列としてまとめて返す。

- 例1: root = [3,9,20,null,null,15,7] → [[3],[9,20],[15,7]]
- 例2: root = [1] → [[1]]
- 例3: root = [] → []
- 制約: ノード数は [0, 2000]、-1000 <= Node.val <= 1000

# 前提
- 答えを見ずに考えて、5分考えて分からなかったら答えを見てください。答えを見て理解したと思ったら、答えを隠して書いてください。筆が進まず5分迷ったら答えを見てください。そして、見ちゃったら一回全部消してやり直しです。答えを送信して、正解になったら、まずは一段階目です。
- 次にコードを読みやすくするようにできるだけ整えましょう。これで動くコードになったら二段階目です。
- そしたらまた全部消しましょう。今度は、時間を測りながら、もう一回、書きましょう。書いてアクセプトされたら文字を消してもう一回書きましょう。これを10分以内に一回もエラーを出さずに書ける状態になるまで続けてください。3回続けてそれができたらその問題はひとまず丸です。


# 1回目
```go
/**
* Definition for a binary tree node.
* type TreeNode struct {
* Val int
* Left *TreeNode
* Right *TreeNode
* }
*/

// 方針: なんとなく,BFSなのだろうという気がするが...
// layer単位でループを回し,BFSを行う.left->rightの順が求められているので,それは気をつける.
// inputが 0 <= N <= 2000
// 時間計算量: 全ノードを辿る必要があるので,O(N)
// 空間計算量: 結果を返すために全ノードの数値を保持する必要があるので,O(N)
func levelOrder(root *TreeNode) [][]int {
result := [][]int{}
frontier := []*TreeNode{root}

for len(frontier) > 0 {
nextLayer := []*TreeNode{}
layerValues := []int{}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

好みの問題ですが、同じ深さであることを示すsameを付けてsameLayerValuessameDepthValuesとしてもいいと思います。

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.

ありがとうございます.たしかにわかりやすくなりそうです


for _, node := range frontier {
if node == nil {
continue
}
layerValues = append(layerValues, node.Val)
nextLayer = append(nextLayer, node.Left)
nextLayer = append(nextLayer, node.Right)
}
frontier = nextLayer
if len(layerValues) != 0 {
result = append(result, layerValues)
}
}
return result
}
```
- 何度かfailさせつつ,passed
- `if len(layerValues) != 0` の漏れなど
- 見直したが,あまり嫌なところはない.強いて言えば`if len(layerValues) != 0`の箇所だが,これは避けられないように思える
- 他の選択肢について思いを馳せる
- 再帰でも一応できるはずだけど,嬉しさなさそう.
- レイヤーごとに処理しないBFSも一応はあるけど,分けて処理していたのは正解
- DFSでも一応解けるんだろうけど,全然自然じゃないと感じるのでこれは考えない

# 2回目
```go
func levelOrder(root *TreeNode) [][]int {
result := [][]int{}
if root == nil {
return result
}
frontier := []*TreeNode{root}

for len(frontier) > 0 {
nextLayer := []*TreeNode{}
layerValues := []int{}

for _, node := range frontier {
layerValues = append(layerValues, node.Val)
if node.Left != nil {
nextLayer = append(nextLayer, node.Left)
}
if node.Right != nil {
nextLayer = append(nextLayer, node.Right)
}
}
frontier = nextLayer
result = append(result, layerValues)
}
return result
}
```
- 全部キューに積んでからnilをskipする.という方針から,そもそもnilは積まないという方針へ
- これは正直どっちでもいいな.あまり綺麗になったとも今のところ思わない.最初にrootのnilチェックするという処理は増えているしなぁ.
- でも一方で,resultをappendするときのifも消せているのか
- 他の人のPRをみる
- https://github.com/chryschron/codings/pull/25
- DFSも解いている.えらい
- iterativeのDFSで,queueにright, leftの順でpushするのが,ちょっとアレと思ったけど,コードは正しかった

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

iterativeなコードはrecursiveで暗黙に行われている関数呼び出し時のstack使用を明示するだけなので、recursive == while+stackが成り立ちます(一応queueでstackをエミュレートすることも可能です)。

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.

ですね.コメントありがとうございます.

このへん自分の理解では,BFS・DFSという軸と,recursive / iterativeという軸があり,それぞれ相互に書き換え可能,という理解でした.「iterativeなコードはrecursiveで暗黙に行われている関数呼び出し時のstack使用を明示するだけ」のような説明ができるようにしておくべきだなと気づけました.

(個人的な感覚ですがBFSならiterativeに,DFSならrecursiveというのが直感的で,ただしスタックオーバーフローを懸念して,DFSもiterativeに書き直す,ということをオンラインコーディング練習会で何度かやりました.)

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ならrecursiveでBFSならiterativeが直感的というよりかは、有限二分木DFSならrecursive/stackだけ(PDA)で解けるがBFSは理論上不可能(PDAよりも強い、追加の線形データ領域を持つ計算機が必要)という認識が正しいです。たとえ二分木BFSをrecursiveに書いたとしても、queue等別の記憶領域を作ってやらないといけません。

@h-masder h-masder Jul 14, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@chryschron さん
コメントの意図を書くと良いと思います。

例えば、

二分木BFSをrecursiveに書いたとしても、queue等別の記憶領域を作ってやる必要がある

という点について、この知識を使うことでコードの改善にどうつながるのか、などです。

また、miyataka さんのコメントは、前後の文脈から見ると、コードの可読性についての話をされているように見えました。もしかみ合っていなかったとして、そういった認識のずれをなくすためにも、コメントの意図を書くとよいと思いました。

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.

@chryschron

DFSならrecursiveでBFSならiterativeが直感的というよりかは、有限二分木DFSならrecursive/stackだけ(PDA)で解けるがBFSは理論上不可能(PDAよりも強い、追加の線形データ領域を持つ計算機が必要)という認識が正しいです。たとえ二分木BFSをrecursiveに書いたとしても、queue等別の記憶領域を作ってやらないといけません。

はい,それはその通りですね.
ちなみに一度目の自分からのコメントの,「(個人的な感覚ですが〜」のくだりは,あくまで自分の好みの話であるので,正しい正しくないの話ではない,と思ってもいます.

@h-masder さんからもコメントしていただいてますが, @chryschron さんのコメントの意図がつかめておらず,一連のやりとりにおいて,自分は変な応答をしてしまっているかもしれません.

どこですれ違ったかわかっていないので,そもそもコメントいただいた,solution.mdのL97-L100あたりの記述について,このとき考えていただろうことを書き下してみます.


  1. PRをみる.ざっとみてstep1はBFSだなと読み取る.
  2. step1をもうちょいみる.これは自分とほぼ同じ回答だなと思う.
  3. step1のコードの下の日本語を読む.DFSを書くんだなと思う.
  4. solution.mdに「DFSも解いている.えらい」と書く
  5. step2の最初のほうのコードを読む.再帰だなぁと思う.
  6. 次のコードブロックを読む(このときにコードブロックの手前(L68)のiterativeは読み飛ばしている)
  7. 再帰とは全体の形が違うので,step3はBFSで書いたのかな?となんとなく思う(これは勘違い)
  8. pushがright,leftの順だ!なぜ?と思う.
  9. solution.mdに「iterativeのDFSで,queueにright, leftの順でpushするのが,ちょっとアレと思ったけど,コードは正しかった」と書く.(なお,ここで「iterativeのDFSで」と書いているものの,自分の文章の矛盾には気づいていない.)
  10. よく読んだら,自分がBFSだと思っていたものはDFSで,自分がqueueだと思っていたものはstackだったと気づく
  11. solution.mdに「よく読んだらstackという命名にちゃんとなっていた」と書く.(このコメントも不正確で,stackは命名ではなくクラスですね)

こう書くと,自分も大概なメモを書いてしまって(そしてそのままにして)いるなぁと思いました.

返信はなくても大丈夫です(労力に見合わないと思うので).もし読んでもらったらemojiリアクションだけもらえるとありがたいです

@chryschron chryschron Jul 15, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

いえ、私もようやく全容を把握しました。
私のコメント意図は最初から一貫していて、 @miyataka さんが最初のプルリクで私のコードに対し『iterativeのDFSで,queueにright, leftの順でpushするのが(略)』『よく読んだらstackという命名にちゃんとなっていた』と仰っており、もしかしてDFSを解くのにwhile+queueを使うと考えている、すなわち二分木DFS=PDA(それゆえwhile+stack or recursiveを直感する)という理論理解に不足があるのではないかと考えたためです。(返信を拝見する限り、釈迦に説法だったようですが)
単にBFSと混同なさっていただけのようですね。失礼いたしました。
@h-masder さん 了解しました。

- よく読んだらstackという命名にちゃんとなっていた
- https://github.com/skypenguins/coding-practice/pull/40
- level_by_level, あまり意図わからんかった.でも修正されてた

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_by_level は分かりづらいですよね…

- https://github.com/Hiroto-Iizuka/coding_practice/pull/26
- step1のコメントはよくわからんかった.
- `node_with_level`という変数名は複数形にしたいと思った
- step3は同じ方針になってそう
- https://github.com/hiroki-horiguchi-dev/leetcode/pull/26
- なぜ階層分けした状態にresultがなるのか,一瞬迷った.外側のループで,nodes.size()するときに,ループ回数が固定されるからだという理解をした.
- https://github.com/nicah4o/arai60/pull/25
- step3が,dfsという関数名なのがすこし気になった.
- https://github.com/tom4649/Coding/pull/146#discussion_r3566048414 のようなこと.実装の詳細ではなく,なにが起きるのか,何を返すのかがわかる命名にしたい.
- https://github.com/dorxyxki/arai60/pull/26
- if/whileの話を読んだ(感想ではない)
- https://github.com/h-masder/Arai60/pull/29
- `if not xxx` と `if xxx is None` がどちらもあるのが気になった.今回は`Optional[TreeNode]`なので `if root is None` に統一でよいのではと思った.python詳しくないけど.
- https://github.com/rimokem/arai60/pull/26
- `node_groups` という命名はコメントにあるとおり
- 別にこの方に限った話ではないのだが,pythonを使っている方で,関数内で別途関数を定義して,関数内部のローカル変数を関数内関数にとってのグローバル変数のようにして再帰などをしている例をよくみる.ある意味,副作用がある関数なので,なるべく避けたい気持ちになる.

# 3回目
```go
func levelOrder(root *TreeNode) [][]int {
result := [][]int{}
if root == nil {
return result
}

frontier := []*TreeNode{root}
for len(frontier) > 0 {
nextLayer := []*TreeNode{}
layerValues := []int{}

for _, node := range frontier {
layerValues = append(layerValues, node.Val)
if node.Left != nil {
nextLayer = append(nextLayer, node.Left)
}
if node.Right != nil {
nextLayer = append(nextLayer, node.Right)
}
}
frontier = nextLayer
result = append(result, layerValues)
}
return result
}
```
- これを3回繰り返した