-
Notifications
You must be signed in to change notification settings - Fork 0
102. Binary Tree Level Order Traversal #25
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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{} | ||
|
|
||
| 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するのが,ちょっとアレと思ったけど,コードは正しかった | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. iterativeなコードはrecursiveで暗黙に行われている関数呼び出し時のstack使用を明示するだけなので、recursive == while+stackが成り立ちます(一応queueでstackをエミュレートすることも可能です)。
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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に書き直す,ということをオンラインコーディング練習会で何度かやりました.) There was a problem hiding this comment. Choose a reason for hiding this commentThe 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等別の記憶領域を作ってやらないといけません。 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @chryschron さん 例えば、
という点について、この知識を使うことでコードの改善にどうつながるのか、などです。 また、miyataka さんのコメントは、前後の文脈から見ると、コードの可読性についての話をされているように見えました。もしかみ合っていなかったとして、そういった認識のずれをなくすためにも、コメントの意図を書くとよいと思いました。
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
はい,それはその通りですね. @h-masder さんからもコメントしていただいてますが, @chryschron さんのコメントの意図がつかめておらず,一連のやりとりにおいて,自分は変な応答をしてしまっているかもしれません. どこですれ違ったかわかっていないので,そもそもコメントいただいた,solution.mdのL97-L100あたりの記述について,このとき考えていただろうことを書き下してみます.
こう書くと,自分も大概なメモを書いてしまって(そしてそのままにして)いるなぁと思いました. 返信はなくても大丈夫です(労力に見合わないと思うので).もし読んでもらったらemojiリアクションだけもらえるとありがたいです There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. いえ、私もようやく全容を把握しました。 |
||
| - よく読んだらstackという命名にちゃんとなっていた | ||
| - https://github.com/skypenguins/coding-practice/pull/40 | ||
| - level_by_level, あまり意図わからんかった.でも修正されてた | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. やっぱり |
||
| - 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回繰り返した | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
好みの問題ですが、同じ深さであることを示す
sameを付けてsameLayerValuesやsameDepthValuesとしてもいいと思います。There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ありがとうございます.たしかにわかりやすくなりそうです