-
Notifications
You must be signed in to change notification settings - Fork 0
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
Maximum Depth of Binary Tree #41
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,32 @@ | ||
//lint:file-ignore U1000 Ignore all unused code | ||
package maximumdepthofbinarytree | ||
|
||
type TreeNode struct { | ||
Val int | ||
Left *TreeNode | ||
Right *TreeNode | ||
} | ||
|
||
/* | ||
レビュワーの方へ: | ||
- このコードは既にGoの標準のフォーマッタで整形済みです。演算子の周りにスペースがあったりなかったりしますが、これはGoのフォーマッタによるもので、優先順位の高い演算子の周りにはスペースが入らず、低い演算子の周りには入るようになっています。https://qiita.com/tchssk/items/77030b4271cd192d0347 | ||
*/ | ||
|
||
/* | ||
時間:2分 | ||
|
||
一番オーソドックスなやり方。 | ||
|
||
1フレームの大きさは、引数8 + 返り値8 + ローカル変数(8 + 8) + FP + 戻りアドレス = 48Bぐらい。最大ノード数が10^4なのでスタックサイズは、48 * 10^4 = 480KBぐらいになると予想できる。 | ||
Linuxのデフォルトのスタックの大きさが8MBなのでスタックオーバーフローの危険性は低い。 | ||
goroutineのデフォルトのスタックの大きさは8KBだが、動的に拡張されるようになっているので通常、スタックサイズがGBレベルになっても問題なく再帰を回すことができる。 | ||
https://github.com/rihib/leetcode/pull/15#issue-2459505166 | ||
*/ | ||
func maxDepthRecursive(root *TreeNode) int { | ||
if root == nil { | ||
return 0 | ||
} | ||
leftDepth := maxDepthRecursive(root.Left) | ||
rightDepth := maxDepthRecursive(root.Right) | ||
return max(leftDepth, rightDepth) + 1 | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
//lint:file-ignore U1000 Ignore all unused code | ||
package maximumdepthofbinarytree | ||
|
||
/* | ||
レビュワーの方へ: | ||
- このコードは既にGoの標準のフォーマッタで整形済みです。演算子の周りにスペースがあったりなかったりしますが、これはGoのフォーマッタによるもので、優先順位の高い演算子の周りにはスペースが入らず、低い演算子の周りには入るようになっています。https://qiita.com/tchssk/items/77030b4271cd192d0347 | ||
*/ | ||
|
||
/* | ||
他の方の解法を見て色々なやり方で解いてみた。 | ||
*/ | ||
/* | ||
DFS | ||
*/ | ||
// pushする前にnilノードを弾く | ||
func maxDepthIterativeDFS(root *TreeNode) int { | ||
if root == nil { | ||
return 0 | ||
} | ||
maximum := 0 | ||
stack := []entry{{root, 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. データ構造を変数名にするのはあまり良くないと思いました 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 や queue という名前は状況次第で許容側です。 一方で、関数名はだいたい詳細に説明して欲しいです。たとえば、dfs() とだけあると、何が返ってくるかが分からないので、dfs の中を読みにいかないといけなくなります。 |
||
for len(stack) > 0 { | ||
e := stack[len(stack)-1] | ||
stack = stack[:len(stack)-1] | ||
maximum = max(maximum, e.depth) | ||
if e.node.Left != nil { | ||
stack = append(stack, entry{e.node.Left, e.depth + 1}) | ||
} | ||
if e.node.Right != nil { | ||
stack = append(stack, entry{e.node.Right, e.depth + 1}) | ||
} | ||
} | ||
return maximum | ||
} | ||
|
||
// popした後にnilノードを弾く | ||
func maxDepthIterativeDFS2(root *TreeNode) int { | ||
maximum := 0 | ||
stack := []entry{{root, 1}} | ||
for len(stack) > 0 { | ||
e := stack[len(stack)-1] | ||
stack = stack[:len(stack)-1] | ||
if e.node == nil { | ||
continue | ||
} | ||
maximum = max(maximum, e.depth) | ||
stack = append(stack, entry{e.node.Left, e.depth + 1}) | ||
stack = append(stack, entry{e.node.Right, e.depth + 1}) | ||
} | ||
return maximum | ||
} | ||
|
||
// 帰りがけにdepthを更新 | ||
func maxDepthIterativeDFS3(root *TreeNode) int { | ||
maximum := 0 | ||
stack := []*entry2{ | ||
{ | ||
node: root, | ||
isPreorder: true, | ||
depth: &maximum, | ||
leftDepth: new(int), | ||
rightDepth: new(int), | ||
}, | ||
} | ||
for len(stack) > 0 { | ||
e := stack[len(stack)-1] | ||
stack = stack[:len(stack)-1] | ||
if e.isPreorder { | ||
if e.node == nil { | ||
continue | ||
} | ||
e.isPreorder = false | ||
stack = append(stack, e) | ||
stack = append(stack, &entry2{ | ||
node: e.node.Left, | ||
isPreorder: true, | ||
depth: e.leftDepth, | ||
leftDepth: new(int), | ||
rightDepth: new(int), | ||
}) | ||
stack = append(stack, &entry2{ | ||
node: e.node.Right, | ||
isPreorder: true, | ||
depth: e.rightDepth, | ||
leftDepth: new(int), | ||
rightDepth: new(int), | ||
}) | ||
} else { | ||
*e.depth = max(*e.leftDepth, *e.rightDepth) + 1 | ||
} | ||
} | ||
return maximum | ||
} | ||
|
||
type entry2 struct { | ||
node *TreeNode | ||
isPreorder bool | ||
depth *int | ||
leftDepth *int | ||
rightDepth *int | ||
} | ||
|
||
/* | ||
BFS | ||
*/ | ||
// pushする前にnilノードを弾く | ||
func maxDepthIterativeBFS(root *TreeNode) int { | ||
if root == nil { | ||
return 0 | ||
} | ||
maximum := 0 | ||
queue := []entry{{root, 1}} | ||
for len(queue) > 0 { | ||
e := queue[0] | ||
queue = queue[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. 自分は Go 言語に詳しくないため質問させていただきたいのですが、このように書いた場合、配列の後半のコピーが作成されるのでしょうか? 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. インデックス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. コピーは作られず、 インデックスとポインタの対応関係が変わるような動作になります。
|
||
maximum = max(maximum, e.depth) | ||
if e.node.Left != nil { | ||
queue = append(queue, entry{e.node.Left, e.depth + 1}) | ||
} | ||
if e.node.Right != nil { | ||
queue = append(queue, entry{e.node.Right, e.depth + 1}) | ||
} | ||
} | ||
return maximum | ||
} | ||
|
||
// popした後にnilノードを弾く | ||
func maxDepthIterativeBFS2(root *TreeNode) int { | ||
maximum := 0 | ||
queue := []entry{{root, 1}} | ||
for len(queue) > 0 { | ||
e := queue[0] | ||
queue = queue[1:] | ||
if e.node == nil { | ||
continue | ||
} | ||
maximum = max(maximum, e.depth) | ||
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. maximum = e.depth |
||
queue = append(queue, entry{e.node.Left, e.depth + 1}) | ||
queue = append(queue, entry{e.node.Right, e.depth + 1}) | ||
} | ||
return maximum | ||
} | ||
|
||
type entry struct { | ||
node *TreeNode | ||
depth int | ||
} | ||
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. 慣例かは知らないですが、確かに最初に定義するのが一般的ですね |
||
|
||
// depthを保持せずに処理する | ||
func maxDepthIterativeBFS3(root *TreeNode) int { | ||
if root == nil { | ||
return 0 | ||
} | ||
maximum := 0 | ||
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. max_depth |
||
queue := []*TreeNode{root} | ||
for len(queue) > 0 { | ||
maximum++ | ||
count := len(queue) | ||
for i := 0; i < count; i++ { | ||
node := queue[0] | ||
queue = queue[1:] | ||
if node.Left != nil { | ||
queue = append(queue, node.Left) | ||
} | ||
if node.Right != nil { | ||
queue = append(queue, node.Right) | ||
} | ||
} | ||
} | ||
return maximum | ||
} |
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.
自分ここら辺の理解が浅いので質問させてください!
ここでLinuxのスタックサイズを考慮するのはなぜですか?
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.
Goはランタイムがよしなにやってくれているだけで、OSスレッド上でプログラムが動作していることには変わりがないので、、
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.
前に教えていただいたGoのスタックサイズの上限をこの時は知らず、調べても出てこなかったので、とりあえずはLinuxのスレッドに割り当てられるスタックのデフォルトサイズを基準に考えたという感じですね
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.
GoがLinuxのスタックサイズを超えてスタックを割り当てられるのは、Goランタイムが適宜ヒープからメモリ領域を割り当てて、スタックサイズを拡張している感じだった気がします(今パッとソースが出てこないのですが、確かそうだったはず)
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.
ありがとうございます。勉強になります!