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
194 changes: 194 additions & 0 deletions minimum-depth-of-binary-tree/memo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
## 問題

[Minimum Depth of Binary Tree - LeetCode](https://leetcode.com/problems/minimum-depth-of-binary-tree/)

- 入力
- `root`: 2分木のルート
- 木のノード数は0以上10^5以下
- ノードの値は-1000以上1000以下
- 出力
- ルートから一番近い葉へのノードの数

## 解法

### 1. BFSで一番近い葉を探す

- 深さごとにBFSで葉を探していく
- ノード数をnとすると
- 時間計算量はO(n)
- すべてのノードを1回ずつ訪問するため
- 空間計算量はO(m): m はある深さにあるノードの数で n より小さい
- キューに入れるノードの数の最大数

## Step1

### 1.

```go
/**
* Definition for a binary tree node.
* type TreeNode struct {
* Val int
* Left *TreeNode
* Right *TreeNode
* }
*/
func minDepth(root *TreeNode) int {
if root == nil {
return 0
}

var depth int
nodes := []*TreeNode{root}

for len(nodes) > 0 {
var nextNodes []*TreeNode
depth++

for _, node := range nodes {
if node.Left == nil && node.Right == nil {
return depth
}

if node.Left != nil {
nextNodes = append(nextNodes, node.Left)
}
if node.Right != nil {
nextNodes = append(nextNodes, node.Right)
}
}

nodes = nextNodes
}

// invalid
return -1
}
```

## Step2

- DFSでも最小値は求められる
- 木全体を走査する
- 実装はラク

### レビューを依頼する方のPR

- [111.Minimum-Depth-of-Binary-Tree by PafsCocotte · Pull Request #4 · PafsCocotte/leetcode](https://github.com/PafsCocotte/leetcode/pull/4/)
- depth のインクリメントのタイミングについて
- 内部のループをある深さの処理として見ているから、ということを考えた
- ノードと深さをまとめてBFSするのもある
- ネストが1段で済む
- [111. Minimum Depth of Binary Tree by TakayaShirai · Pull Request #22 · TakayaShirai/leetcode_practice](https://github.com/TakayaShirai/leetcode_practice/pull/22)
- [111. Minimum Depth of Binary Tree by dxxsxsxkx · Pull Request #22 · dxxsxsxkx/leetcode](https://github.com/dxxsxsxkx/leetcode/pull/22)
- DFSで minDepth を更新しながら進める方法
- [111. Minimum Depth of Binary Tree by Yuto729 · Pull Request #27 · Yuto729/LeetCode_arai60](https://github.com/Yuto729/LeetCode_arai60/pull/27)
- [111. Minimum Depth of Binary Tree by mamo3gr · Pull Request #20 · mamo3gr/arai60](https://github.com/mamo3gr/arai60/pull/20)

### 深さを持つ構造体を使った場合

- 長いのと、参照を取り除く処理が入るのがノイズに感じた
- go だとネストが深くても2重ループにするほうが好み

```go
/**
* Definition for a binary tree node.
* type TreeNode struct {
* Val int
* Left *TreeNode
* Right *TreeNode
* }
*/
func minDepth(root *TreeNode) int {
if root == nil {
return 0
}

type nodeWithDepth struct {
node *TreeNode
depth int
}

nodes := []nodeWithDepth{
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

nodesという名前だと、nodeの配列に感じて、nodeWithDepthの配列だというのは誤解を生むように思えました。

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.

ありがとうございます。

読む時に違和感を感じないよう nodesWithDepth などのほうが適切そうですね。

{
node: root,
depth: 1,
},
}

for len(nodes) > 0 {
current := nodes[0]
// GC対象にするため参照を外す
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

このテクニック知らなかったので勉強になりました!メモリリークが起きないようにしているんですね

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

これよく知らなかったのですが、ここの SliceTricks に
https://go.dev/wiki/SliceTricks

NOTE If the type of the element is a pointer or a struct with pointer fields, which need to be garbage collected, the above implementations of Cut and Delete have a potential memory leak problem: some elements with values are still referenced by slice a’s underlying array, just not “visible” in the slice. Because the “deleted” value is referenced in the underlying array, the deleted value is still “reachable” during GC, even though the value cannot be referenced by your code. If the underlying array is long-lived, this represents a leak. The following code can fix this problem:

とありました。

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

はい、Go のスライスは underlying array を持つ構造になっているのでこういうテクニックが必要になるのでしょうね。

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.

みなさまコメントありがとうございます。

おそらく私が最初にこのやり方を知ったのは優先度付きキューの実装例に同様の記載があったことだったかと思います。
記載いただいた通りスライスの構造に由来する処理と理解しています。

https://pkg.go.dev/container/heap

// don't stop the GC from reclaiming the item eventually

nodes[0].node = nil
nodes = nodes[1:]

node := current.node
depth := current.depth

if node.Left == nil && node.Right == nil {
return depth
}

if node.Left != nil {
nodes = append(nodes, nodeWithDepth{
node: node.Left,
depth: depth + 1,
})
}
if node.Right != nil {
nodes = append(nodes, nodeWithDepth{
node: node.Right,
depth: depth + 1,
})
}
}

// invalid
return -1
}
```


## Step3

```go
/**
* Definition for a binary tree node.
* type TreeNode struct {
* Val int
* Left *TreeNode
* Right *TreeNode
* }
*/
func minDepth(root *TreeNode) int {
if root == nil {
return 0
}

nodes := []*TreeNode{root}
depth := 1

for len(nodes) > 0 {
var nextNodes []*TreeNode
for _, node := range nodes {
if node.Left == nil && node.Right == nil {
return depth
}

if node.Left != nil {
nextNodes = append(nextNodes, node.Left)
}
if node.Right != nil {
nextNodes = append(nextNodes, node.Right)
}

nodes = nextNodes
}

depth++
}

// invalid
return -1
Comment on lines +191 to +192
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

自分ならpanicしちゃうかなあと思いました。

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.

ありがとうございます。

一般的には error を返すインターフェースにすると思うので、ちょっと書きづらいところではあります。
この練習会では到達不能なコードには panic を使うのが assert 的な意図も伝えられレビュワーにとっては違和感なさそうなので、次回からはその方針にしようと思いました。
(実務では panic はほぼ使わないので、書くのに躊躇する感じでした)

}
```