Skip to content
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

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
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
32 changes: 32 additions & 0 deletions pullrequests/maximum_depth_of_binary_tree/step1.go
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なのでスタックオーバーフローの危険性は低い。
Copy link

Choose a reason for hiding this comment

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

自分ここら辺の理解が浅いので質問させてください!
ここでLinuxのスタックサイズを考慮するのはなぜですか?

Copy link
Owner Author

Choose a reason for hiding this comment

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

Goはランタイムがよしなにやってくれているだけで、OSスレッド上でプログラムが動作していることには変わりがないので、、

Copy link
Owner Author

Choose a reason for hiding this comment

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

前に教えていただいたGoのスタックサイズの上限をこの時は知らず、調べても出てこなかったので、とりあえずはLinuxのスレッドに割り当てられるスタックのデフォルトサイズを基準に考えたという感じですね

Copy link
Owner Author

Choose a reason for hiding this comment

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

GoがLinuxのスタックサイズを超えてスタックを割り当てられるのは、Goランタイムが適宜ヒープからメモリ領域を割り当てて、スタックサイズを拡張している感じだった気がします(今パッとソースが出てこないのですが、確かそうだったはず)

Copy link

Choose a reason for hiding this comment

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

ありがとうございます。勉強になります!

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
}
171 changes: 171 additions & 0 deletions pullrequests/maximum_depth_of_binary_tree/step2.go
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}}
Copy link

Choose a reason for hiding this comment

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

データ構造を変数名にするのはあまり良くないと思いました

Copy link

Choose a reason for hiding this comment

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

私は、比較的、stack や queue という名前は状況次第で許容側です。
何が入っているかが分かって、その後でどのように使われるか(後ろで出し入れする)のヒントになっています。
そうすると、その後を読む流れをあまり阻害しないからです。
ただ、どういう物が入っているか、説明するのが複雑であるならば、何が入っている stack なのか説明が欲しくなります。

一方で、関数名はだいたい詳細に説明して欲しいです。たとえば、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:]
Copy link

Choose a reason for hiding this comment

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

自分は Go 言語に詳しくないため質問させていただきたいのですが、このように書いた場合、配列の後半のコピーが作成されるのでしょうか?

Choose a reason for hiding this comment

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

インデックス1以降の要素が含まれた動的配列が代入されます。
https://go.dev/ref/spec#Slice_expressions

Choose a reason for hiding this comment

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

コピーは作られず、 インデックスとポインタの対応関係が変わるような動作になります。

Slicing does not copy the slice’s data. It creates a new slice value that points to the original array. This makes slice operations as efficient as manipulating array indices. Therefore, modifying the elements (not the slice itself) of a re-slice modifies the elements of the original slice:

d := []byte{'r', 'o', 'a', 'd'}
e := d[2:]
// e == []byte{'a', 'd'}
e[1] = 'm'
// e == []byte{'a', 'm'}
// d == []byte{'r', 'o', 'a', 'm'}

https://go.dev/blog/slices-intro

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)

Choose a reason for hiding this comment

The 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
}
Copy link

Choose a reason for hiding this comment

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

慣例として構造体は使用箇所より前で定義すると思います

Copy link
Owner Author

Choose a reason for hiding this comment

The 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

Choose a reason for hiding this comment

The 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
}