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
227 changes: 227 additions & 0 deletions 104. Minimum Depth of Binary Tree/Minimum Depth of Binary Tree.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
# 104. Minimum Depth of Binary Tree
https://leetcode.com/problems/minimum-depth-of-binary-tree/description/

## STEP1
- 何も見ずに解いてみる

#### 考えたこと
- ひとつ前の問題(木の最大の深さを求める)と同様の手法が使えそう
- BFSをして、そのノードのrightもleftもnullなものがあった時点で、その時点の階層を返せばよい

計算量
- 時間計算量 O(N)
- ステップで言うと頂点+辺の数なので 3Nステップ
- N = 10^5のとき0.3ミリ秒と見積り

- 空間計算量 O(N)


```cpp

#include <algorithm>
#include <vector>

class Solution {
public:
int minDepth(TreeNode* root) {
if (!root) {
return 0;
}
std::vector<TreeNode*> current_nodes;
current_nodes.push_back(root);
int depth = 1;
while (!current_nodes.empty()) {
std::vector<TreeNode*> next_nodes;
for (auto node : current_nodes) {
if (!node->right && !node->left) {
return depth;
}
if (node->left) {
next_nodes.push_back(node->left);
}
if (node->right) {
next_nodes.push_back(node->right);
}
}
depth++;
std::swap(current_nodes, next_nodes);
}
return -1; //input_error
}
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

良いと思います。

だいぶ細かいところで恐縮ですが、36行目はleftからrightにしたくなりました。

処理が複雑になっていった時に、操作の順序が意図的なのか混乱する気がするというのが理由です。

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.

ありがとうございます。
こちらはおっしゃる通りですね、特に意味がない限り、統一してleft,rightにします。


```

## STEP2
### プルリクやドキュメントを参照
#### 問題が解けるより他人のコードを読んだりコメントするほうがよっぽど大事
#### 参照したもの

- https://github.com/ichika0615/arai60/pull/16/files
- https://github.com/Fuminiton/LeetCode/pull/22/files
- https://github.com/fuga-98/arai60/pull/22/files
- https://github.com/ryoooooory/LeetCode/pull/25/files

- ドキュメント系

teachers' eye
- どんな入力を想定していますか? https://github.com/Fuminiton/LeetCode/pull/22/files#r1996628892
- 例外を狭める https://github.com/Fuminiton/LeetCode/pull/22/files#r1997354335
- 例外処理に関する議論 https://github.com/fuga-98/arai60/pull/22/files#r1995974879



#### 感想
- 「入力が木じゃない」ことを考えもしなかった。入力の想定が甘い。
- 関数の最後の行に到達することが想定されない場合はどうすればいいんだろう。
- 今回は木がループの場合は到達しないが、その場合はコードが終わらない
- ループを判定するにはseen_nodesのようなものを管理すればよい
- pythonは返り値なしも許されたり、返り値なし&エラー投げるとかできるようだ
- 無限ループにすることで最後の1行が消せることが判明 https://github.com/ryoooooory/LeetCode/pull/25/files#r1981036882
- GPTに聞いたら、無限ループにすると、必ずwhile中のreturnで関数が終了すると解析できるため、らしい。

#### STEP1以外の手法と感想
- BFS以外にはDFSがある
- 再帰の上限は今の制約上は問題ない。
- ただしDFSは全てのノードを探索する必要があり、枝刈りはできない。
- コードはDFSのほうが簡潔
- 自然言語の説明的にも合致するのはBFSかなと思う。

STEP1の改良(無限ループ版)
```cpp

#include <algorithm>
#include <vector>

class Solution {
public:
int minDepth(TreeNode* root) {
if (!root) {
return 0;
}
std::vector<TreeNode*> current_nodes;
current_nodes.push_back(root);
int depth = 1;
while (true) {
std::vector<TreeNode*> next_nodes;
for (auto node : current_nodes) {
if (!node->right && !node->left) {
return depth;
}
if (node->left) {
next_nodes.push_back(node->left);
}
if (node->right) {
next_nodes.push_back(node->right);
}
}
depth++;
std::swap(current_nodes, next_nodes);
}
}
};

```

DFSでも実装
```cpp

#include <algorithm>

class Solution {
public:
int minDepth(TreeNode* root) {
if (!root) {
return 0;
}
return MeasureMinTreeDepth(root, 1);
}
private:
int MeasureMinTreeDepth(TreeNode* node, int depth) {
if (!node->left && !node->right) {
return depth;
}
int min_depth = INT_MAX;
if (node->right) {
min_depth = std::min(MeasureMinTreeDepth(node->right, depth + 1), min_depth);
}
if (node->left) {
min_depth = std::min(MeasureMinTreeDepth(node->left, depth + 1), min_depth);
}
return min_depth;
}
};

```

もっとシンプルに書ける

```cpp

#include <algorithm>

class Solution {
public:
int minDepth(TreeNode* root) {
if (!root) {
return 0;
}
if (!root->left) {
return 1 + minDepth(root->right);
}
if (!root->right) {
return 1 + minDepth(root->left);
}
return 1 + std::min(minDepth(root->right), minDepth(root->left));
}
};


```


## STEP3
### 3回ミスなく書く
自然なBFSで
```cpp

#include <algorithm>
#include <vector>

class Solution {
public:
int minDepth(TreeNode* root) {
if (!root) {
return 0;
}
std::vector<TreeNode*> current_nodes;
current_nodes.push_back(root);
int depth = 1;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

depthは0始まりであってほしいという指摘を受けたことかあります。
0始まりだと更新タイミングがわかりにくくなってしまうので、趣味だと思います。
olsen-blue/Arai60#27 (comment)

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.

ありがとうございます。
自分の感覚では、swapの操作とdepthのインクリメントを1ループの締めの処理としてまとめた方が見やすいと感じているので、こうしてみました。

while (true) {
std::vector<TreeNode*> next_nodes;
for (auto node : current_nodes) {
if (!node->left && !node->right) {
return depth;
}
if (node->left) {
next_nodes.push_back(node->left);
}
if (node->right) {
next_nodes.push_back(node->right);
}
Comment on lines +206 to +211
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

とりあえず push_back して、出てきたものが nullptr でないかを確認するというのもありでしょう。

今回、特に問題ないかと思います。

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.

ありがとうございます。
以下のようにできることを確認しました。配列に無効なノードは入りますが、例外処理をまとめて行えるので今回はこちらが良いと思いました。どうしても自分は配列に入るものが有効である保証があることを考えるのですが、今回のように、そうでなくても良いか、それで楽になる部分はないか考えてみようと思います。

class Solution {
public:
    int minDepth(TreeNode* root) {
        if (!root) {
            return 0;
        }
        std::vector<TreeNode*> current_nodes;
        current_nodes.push_back(root);
        int depth = 1;
        while (true) {
            std::vector<TreeNode*> next_nodes;
            for (auto node : current_nodes) {
                if (!node) {
                    continue;
                }
                if (!node->left && !node->right) {
                    return depth;
                }
                next_nodes.push_back(node->left);
                next_nodes.push_back(node->right);
            }
            depth++;
            std::swap(current_nodes, next_nodes);
        }
    }
};

}
depth++;
std::swap(current_nodes, next_nodes);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

current_nodes.swap(next_nodes); というのもあります。

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.

ありがとうございます。こちらはstd::vectorのメンバ関数と理解いています。
current "を" next "と" 入れ替えるニュアンス出せると思いました

}
}
};


```

5分,4分,4分で3回Accept

#### 2週目の宿題

- 非再帰DFSでも実装する
- 例外処理の方法についても、何を想定しているか、どんな形で処理するのか、等々、他のDiscordを見たり、自分調べたりして学ぶ