-
Notifications
You must be signed in to change notification settings - Fork 0
103. Binary Tree Zigzag Level Order Traversal #26
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,5 @@ | ||
| start_new_problem.sh | ||
| main.go | ||
| go.mod | ||
| go.sum | ||
| *.go |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,287 @@ | ||
| 以下のコードは全てGoのデフォルトフォーマッターgofmtにかけてあります。 | ||
|
|
||
| ```Go | ||
| // Definition for a binary tree node. | ||
| type TreeNode struct { | ||
| Val int | ||
| Left *TreeNode | ||
| Right *TreeNode | ||
| } | ||
| ``` | ||
|
|
||
| ### Step 1 | ||
| - 方針: BFSで各レベルを左から右へ探索して値をスライスに溜める。 | ||
| レベル(0-index)が奇数だったらスライスをreverseする。 | ||
| - 気にしたこと: slices.Reverseの処理が重くならないかどうか確認した。 | ||
| slices.Reverseは線形時間で実行できる。 | ||
| 入力条件より、木のノード数は高々2000で、最大ノード数を持つレベルのノード数は1000未満。 | ||
| 要素数1000のスライスに対するReverseは、CPUのクロック周波数1e8psを考慮すると、1000 / 1e8 = 1e-5 = 10μs。 | ||
| これだけ短い時間で処理できるなら大丈夫。 | ||
| - n: ノード数, k: 最大ノード数を持つレベルのノード数 | ||
| - 時間計算量: O(nk) | ||
| - 空間計算量: O(n) | ||
|
|
||
| ```Go | ||
| func zigzagLevelOrder(root *TreeNode) [][]int { | ||
| if root == nil { | ||
| return [][]int{} | ||
| } | ||
| zigzagLevelNodes := [][]int{} | ||
| level := 0 | ||
| currentLevelNodes := []*TreeNode{root} | ||
| for len(currentLevelNodes) > 0 { | ||
| currentLevelNodeValues := []int{} | ||
| nextLevelNodes := []*TreeNode{} | ||
| for _, node := range currentLevelNodes { | ||
| currentLevelNodeValues = append(currentLevelNodeValues, node.Val) | ||
| if node.Left != nil { | ||
| nextLevelNodes = append(nextLevelNodes, node.Left) | ||
| } | ||
| if node.Right != nil { | ||
| nextLevelNodes = append(nextLevelNodes, node.Right) | ||
| } | ||
| } | ||
| if level%2 == 1 { | ||
|
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.
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. なるほどです。実装してみました func zigzagLevelOrder(root *TreeNode) [][]int {
if root == nil {
return [][]int{}
}
zigzagLevelNodes := [][]int{}
currentLevelNodes := []*TreeNode{root}
for len(currentLevelNodes) > 0 {
currentLevelNodeValues := []int{}
nextLevelNodes := []*TreeNode{}
for _, node := range currentLevelNodes {
currentLevelNodeValues = append(currentLevelNodeValues, node.Val)
if node.Left != nil {
nextLevelNodes = append(nextLevelNodes, node.Left)
}
if node.Right != nil {
nextLevelNodes = append(nextLevelNodes, node.Right)
}
}
zigzagLevelNodes = append(zigzagLevelNodes, currentLevelNodeValues)
currentLevelNodes = nextLevelNodes
}
for i, _ := range zigzagLevelNodes {
if i%2 == 1 {
slices.Reverse(zigzagLevelNodes[i])
}
}
return zigzagLevelNodes
} |
||
| slices.Reverse(currentLevelNodeValues) | ||
| } | ||
| zigzagLevelNodes = append(zigzagLevelNodes, currentLevelNodeValues) | ||
| level++ | ||
| currentLevelNodes = nextLevelNodes | ||
| } | ||
| return zigzagLevelNodes | ||
| } | ||
| ``` | ||
|
|
||
| ### Step 2 | ||
| #### 2a reverse実装 | ||
| ```Go | ||
| func zigzagLevelOrder(root *TreeNode) [][]int { | ||
| if root == nil { | ||
| return [][]int{} | ||
| } | ||
| zigzagLevelNodes := [][]int{} | ||
| level := 0 | ||
| currentLevelNodes := []*TreeNode{root} | ||
| for len(currentLevelNodes) > 0 { | ||
| currentLevelNodeValues := []int{} | ||
| nextLevelNodes := []*TreeNode{} | ||
| for _, node := range currentLevelNodes { | ||
| currentLevelNodeValues = append(currentLevelNodeValues, node.Val) | ||
| if node.Left != nil { | ||
| nextLevelNodes = append(nextLevelNodes, node.Left) | ||
| } | ||
| if node.Right != nil { | ||
| nextLevelNodes = append(nextLevelNodes, node.Right) | ||
| } | ||
| } | ||
| if level%2 == 1 { | ||
| // slices.Reverse(currentLevelNodeValues) | ||
| reverse(¤tLevelNodeValues) | ||
| } | ||
| zigzagLevelNodes = append(zigzagLevelNodes, currentLevelNodeValues) | ||
| level++ | ||
| currentLevelNodes = nextLevelNodes | ||
| } | ||
| return zigzagLevelNodes | ||
| } | ||
|
|
||
| func reverse(s *[]int) { | ||
| i := 0 | ||
| j := len(*s) - 1 | ||
| for i < j { | ||
| (*s)[i], (*s)[j] = (*s)[j], (*s)[i] | ||
| i++ | ||
| j-- | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| #### 2b reverse呼ばない | ||
| - 参考: https://github.com/seal-azarashi/leetcode/pull/26/files#diff-39117f806e1c76b27224df2000d2200427b950646feb7d63be476d764d29dfa8R14 | ||
| - 関数呼び出しのオーバーヘッドがなくなる | ||
|
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. これ時間にするとどれくらいでしょうか。
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. 手元で実験してみたところ、4nsという数字が出てきました start := time.Now()
slices.Reverse(currentLevelNodeValues)
functionCallTime += time.Since(start)start := time.Now()
for i, j := 0, len(currentLevelNodeValues)-1; i < j; i, j = i+1, j-1 {
currentLevelNodeValues[i], currentLevelNodeValues[j] = currentLevelNodeValues[j], currentLevelNodeValues[i]
}
functionCallTime += time.Since(start)
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. 遠い記憶を掘り出してt検定をしてみたらp値0.95 > 0.05で両者に有意差がないという帰無仮説が受諾されました 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. そうですね。 |
||
| - その代わり読みにくい | ||
|
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. 個人的には気になるほどの読みにくさは感じませんでした。ここをどうするかについてはオーダーに影響するので、上の oda さんのレビューに返答するのと合わせて、読みやすさのために処理効率を犠牲にしていいのか考えてもいいと思います。
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. step1のコードと2bのコードの実行時間を、深さ10、要素数2^10-1個の木で計測したところ、2bの方が平均6μs短かったです。一応t検定してみたらp値0.395 > 0.05で優位差はなさそうでした |
||
|
|
||
| ```Go | ||
| func zigzagLevelOrder(root *TreeNode) [][]int { | ||
| if root == nil { | ||
| return [][]int{} | ||
| } | ||
| result := [][]int{} | ||
| level := 0 | ||
| currentLevelNodes := []*TreeNode{root} | ||
| for len(currentLevelNodes) > 0 { | ||
| currentLevelNodeCount := len(currentLevelNodes) | ||
| currentLevelNodeValues := make([]int, 0, currentLevelNodeCount) | ||
| nextLevelNodes := []*TreeNode{} | ||
| for i := 0; i < currentLevelNodeCount; i++ { | ||
| if level%2 == 0 { | ||
| currentLevelNodeValues = append(currentLevelNodeValues, currentLevelNodes[i].Val) | ||
| } else { | ||
| currentLevelNodeValues = append(currentLevelNodeValues, currentLevelNodes[currentLevelNodeCount-i-1].Val) | ||
| } | ||
| if currentLevelNodes[i].Left != nil { | ||
| nextLevelNodes = append(nextLevelNodes, currentLevelNodes[i].Left) | ||
| } | ||
| if currentLevelNodes[i].Right != nil { | ||
| nextLevelNodes = append(nextLevelNodes, currentLevelNodes[i].Right) | ||
| } | ||
| } | ||
| result = append(result, currentLevelNodeValues) | ||
| level++ | ||
| currentLevelNodes = nextLevelNodes | ||
|
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. なかなか難しいですが、currentLevelNodesとcurrentLevelNodeValuesがよく似てるので一瞬わからなくなりそうですね。
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. たしかにそうですね、思い切ってcurrentLevelを省いてnodesとvalues(nodeValues)にしてしまうのもありかもしれません |
||
| } | ||
| return result | ||
| } | ||
| ``` | ||
|
|
||
| #### 2c 再帰 | ||
| - 参考: https://github.com/seal-azarashi/leetcode/pull/26/files#diff-39117f806e1c76b27224df2000d2200427b950646feb7d63be476d764d29dfa8R69 | ||
| - 思いつかなかった | ||
| - 再帰よりstep1のようなBFSの方がレベルごとの探索をより直感的にできるのでベスト解答ではないと思う | ||
| - 再帰の練習として実装してみる | ||
| - 再帰の深さは木の深さとなる | ||
|
|
||
| ```Go | ||
| func zigzagLevelOrder(root *TreeNode) [][]int { | ||
| if root == nil { | ||
| return [][]int{} | ||
| } | ||
| zigzagLevelOrderValues := [][]int{} | ||
| zigzagLevelOrderRecursively(root, 0, &zigzagLevelOrderValues) | ||
| return zigzagLevelOrderValues | ||
| } | ||
|
|
||
| func zigzagLevelOrderRecursively(node *TreeNode, level int, zigzagLevelOrderValues *[][]int) { | ||
| for len(*zigzagLevelOrderValues) <= level { | ||
| *zigzagLevelOrderValues = append(*zigzagLevelOrderValues, []int{}) | ||
| } | ||
| if level%2 == 0 { | ||
| (*zigzagLevelOrderValues)[level] = append((*zigzagLevelOrderValues)[level], node.Val) | ||
| } else { | ||
| (*zigzagLevelOrderValues)[level] = append([]int{node.Val}, (*zigzagLevelOrderValues)[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. この行が全部作り直しているので遅いですかね。 |
||
| } | ||
| if node.Left != nil { | ||
| zigzagLevelOrderRecursively(node.Left, level+1, zigzagLevelOrderValues) | ||
| } | ||
| if node.Right != nil { | ||
| zigzagLevelOrderRecursively(node.Right, level+1, zigzagLevelOrderValues) | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| #### 2d BFS reverse使わない | ||
| - levelの偶奇によって、currentLevelNodeValuesに前から加えるか後ろから加えるかを決める | ||
|
|
||
| ```Go | ||
| func zigzagLevelOrder(root *TreeNode) [][]int { | ||
| if root == nil { | ||
| return [][]int{} | ||
| } | ||
| order := [][]int{} | ||
| level := 0 | ||
| currentLevelNodes := []*TreeNode{root} | ||
| for len(currentLevelNodes) > 0 { | ||
| currentLevelNodeValues := []int{} | ||
| nextLevelNodes := []*TreeNode{} | ||
| for _, node := range currentLevelNodes { | ||
| if level%2 == 0 { | ||
| currentLevelNodeValues = append(currentLevelNodeValues, node.Val) | ||
| } else { | ||
| currentLevelNodeValues = append([]int{node.Val}, currentLevelNodeValues...) | ||
| } | ||
| if node.Left != nil { | ||
| nextLevelNodes = append(nextLevelNodes, node.Left) | ||
| } | ||
| if node.Right != nil { | ||
| nextLevelNodes = append(nextLevelNodes, node.Right) | ||
| } | ||
| } | ||
| order = append(order, currentLevelNodeValues) | ||
| level++ | ||
| currentLevelNodes = nextLevelNodes | ||
| } | ||
| return order | ||
| } | ||
| ``` | ||
|
|
||
| #### 2e BFS zigzag探索 | ||
|
|
||
| ```Go | ||
| func zigzagLevelOrder(root *TreeNode) [][]int { | ||
| if root == nil { | ||
| return [][]int{} | ||
| } | ||
| order := [][]int{} | ||
| level := 0 | ||
| currentLevelNodes := []*TreeNode{root} | ||
| for len(currentLevelNodes) > 0 { | ||
| currentLevelNodeValues := []int{} | ||
| nextLevelNodes := []*TreeNode{} | ||
| for i := 0; i < len(currentLevelNodes); i++ { | ||
| currentLevelNodeValues = append(currentLevelNodeValues, currentLevelNodes[i].Val) | ||
| if level%2 == 0 { | ||
| rightToLeft(currentLevelNodes[len(currentLevelNodes)-i-1], &nextLevelNodes) | ||
| } else { | ||
| leftToRight(currentLevelNodes[len(currentLevelNodes)-i-1], &nextLevelNodes) | ||
| } | ||
| } | ||
| order = append(order, currentLevelNodeValues) | ||
| level++ | ||
| currentLevelNodes = nextLevelNodes | ||
| } | ||
| return order | ||
| } | ||
|
|
||
| func leftToRight(node *TreeNode, nextLevelNodes *[]*TreeNode) { | ||
| if node.Left != nil { | ||
| *nextLevelNodes = append(*nextLevelNodes, node.Left) | ||
| } | ||
| if node.Right != nil { | ||
| *nextLevelNodes = append(*nextLevelNodes, node.Right) | ||
| } | ||
| } | ||
|
|
||
| func rightToLeft(node *TreeNode, nextLevelNodes *[]*TreeNode) { | ||
| if node.Right != nil { | ||
| *nextLevelNodes = append(*nextLevelNodes, node.Right) | ||
| } | ||
| if node.Left != nil { | ||
| *nextLevelNodes = append(*nextLevelNodes, node.Left) | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ### Step 3 | ||
| - BFSでlevelが奇数だったらreverseしてorderに入れる方法。 | ||
| このやり方が最も自然だろうと思った | ||
|
|
||
| ```Go | ||
| func zigzagLevelOrder(root *TreeNode) [][]int { | ||
| if root == nil { | ||
| return [][]int{} | ||
| } | ||
| order := [][]int{} | ||
| level := 0 | ||
| currentLevelNodes := []*TreeNode{root} | ||
| for len(currentLevelNodes) > 0 { | ||
| currentLevelNodeValues := []int{} | ||
| nextLevelNodes := []*TreeNode{} | ||
| for _, node := range currentLevelNodes { | ||
| currentLevelNodeValues = append(currentLevelNodeValues, node.Val) | ||
| if node.Left != nil { | ||
| nextLevelNodes = append(nextLevelNodes, node.Left) | ||
| } | ||
| if node.Right != nil { | ||
| nextLevelNodes = append(nextLevelNodes, node.Right) | ||
| } | ||
| } | ||
| if level%2 == 1 { | ||
| slices.Reverse(currentLevelNodeValues) | ||
| } | ||
| order = append(order, currentLevelNodeValues) | ||
| 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. 好みですが、数値型の level の代わりに、boolean 型の変数を使ってもいいと思いました。 |
||
| currentLevelNodes = nextLevelNodes | ||
| } | ||
| return order | ||
| } | ||
| ``` | ||
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.
このクロック周波数はどのような環境を想定していますか?現在のCPUは1GHzくらいは普通にあるのかなという感覚値でいます。
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.
ここの記述は、以前nodchipさんにされた指摘 #9 (comment) を参考にして算出していました
正直ちゃんと環境を想定できていたわけではなかったです。自分の使っているM1チップについて調べたところ、3.2GHzでした
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.
環境を想定しましょうということではなく、10^8だと100MHzで結構遅いなと思ったのでどういうマシンを想定しているんだろうと気になったというのが背景になります。ただ自分の使っているPCのメモリやコア数などは知っておくと良いというか、普通は興味をもつはずみたいなことを講師陣の方はいうと思いますし、自分もそう思います。
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.
i486 の1994年頃のモデルが 100MHz です。
2005年にインテルから出ている初期のマルチコア CPU が Pentium D が 3 GHz 前後です。
周波数は、光の速度 約 3*10^8 m/s との関係で、3 GHz だと1クロックに 10 cm になってきてそろそろ色々と限界といわれていたので、コアを増やす方向に動いていったというのが歴史です。
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.
クロック周波数は最大でも9GHzくらいなんですね。
https://www.itmedia.co.jp/pcuser/articles/2310/22/news029.html