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
150 changes: 150 additions & 0 deletions 104/104. Maximum Depth of Binary Tree.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
# [104. Maximum Depth of Binary Tree](https://leetcode.com/problems/maximum-depth-of-binary-tree/description/)
## Step1
### 問題意図の考察
- 問題文の確認
- 与えられた二分木の根に対して、最大深度を返す。
- 二分木の最大深度 = root nodeから最も遠い leaf nodeまで

- 制約の確認
- nodeの数は、0 ~ 10^4
- 各nodeの値は、-100 ~ 100

- 問題意図
- nodeの数に注意
- 左部分ぽい所の深さを求める
- 右部分ぽい所の深さを求める
- そのうち大きい方を返す

### 解法を考える。
- DFS
- 再帰で返す方法 シンプルで良さそう
- BFS
- 何段階のレベルがあるか分かればよい
- level1 -> level2 -> level3

初回の回答
```cpp
#include <algorithm>

class Solution {
public:
int maxDepth(TreeNode* root) {
if (root == nullptr) {
return 0;
}

int left = maxDepth(root->left);
int right = maxDepth(root->right);

return std::max(left, right) + 1;
}
};

```

### コメント
- TreeNode の定義内で、left, rightが使用されているので、命名が良くないかな。
- left_depth, right_depthくらいが分かりやすいかもしれない
- if (root = nullptr) に関して
- https://github.com/nktr-cp/leetcode/pull/22/files#diff-d16010dc25fbb50a03680c436bbb0d90f462bf27b3aa26421f1df94d5f15aa15
- 他の方のcodeで、if (!root) という記述があった。違いを少し考えた。
- ポインタが null -> false -> true (if条件成立)
- root を nullptrと比較

```cpp
#include <algorithm>

class Solution {
public:
int maxDepth(TreeNode* root) {
if (!root) {
return 0;
}

int left_depth = maxDepth(root->left);
int right_depth = maxDepth(root->right);

return std::max(left_depth, right_depth) + 1;
}
};

```

## Step2 *2段階目、自然な書き方を考えて整理する
- 改善点
- https://github.com/nktr-cp/leetcode/pull/22/files#diff-d16010dc25fbb50a03680c436bbb0d90f462bf27b3aa26421f1df94d5f15aa15
- もっとシンプルに書いている例があった。
```cpp
class Solution {
public:
int maxDepth(TreeNode* root) {
if (root == nullptr) {
return 0;
}
Comment on lines +81 to +83
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

docto-rinさんが言及されていた「Nullチェックをpop直後に行う」スタイルをとるとこの例外処理は省略可能ですね.

return std::max(maxDepth(root->left), maxDepth(root->right)) + 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.

この書き方と,Step1の書き方のどちらが良いか(1行にまとめる処理の最大数はどれくらいか)は好みが分かれるところだと思います.私はこのくらいならStep2の方がわかりやすいと感じます.

}
};

```

- 他の方のコードを読む
- 前述:https://github.com/nktr-cp/leetcode/pull/22/files
- https://github.com/5103246/LeetCode_Arai60/pull/20/commits/cb2a3f1f447cd59e8d6ac1e2910262a6a05ca7b4
- https://github.com/5ky7/arai60/pull/20/commits/6c2084a4cf7e5760e2d5130518cda59496833be6
- https://github.com/ryosuketc/leetcode_arai60/pull/21/commits/333e716a565c4c0cdb627e8d378410010fd64588
- https://github.com/nanae772/leetcode-arai60/pull/21/commits/9cc3d36c42d10bef83b97d017a73b65d5c0e52af
> 木の高さと深さの違いがよく分かってなかったが
> nodeの高さ=nodeから最も遠い葉までのパスの長さ
> 木の高さ=rootから最も遠い葉までのパスの長さ
> nodeの深さ=nodeからrootまでのパスの長さ
- ここはしっかりと押さえておきたい
- (要復習)
- C/C++だとポインタを持てるのでそのポインタを通じて親への伝播が自動的に行える。
- https://discord.com/channels/1084280443945353267/1227073733844406343/1236324993839792149

- BFS(Breadth-First Search)の書き方
- 分からなかったので、参照
- https://cp-algorithms.com/graph/breadth-first-search.html
- https://www.geeksforgeeks.org/dsa/breadth-first-search-or-bfs-for-a-graph/

```cpp
class Solution {
public:
int maxDepth(TreeNode* root) {
if (root == nullptr) {
return 0;
}

std::queue<TreeNode*> q;
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をpairでまとめる書き方もあります。
pairでまとめると、whileの中のループがなくなります。

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

変数名のみから中身が想像できるのが好みですが,これくらいのコードなら問題ないとも思います.
私はDFSやBFSには「探索完了ノードと未完了ノードの境界」=「探索の最前線」という解釈でfrontierという変数名を用いるのが好みです.

q.push(root);

int depth = 0;

while (!q.empty()) {
int level_size = q.size();
++depth;

for (int i = 0; i < level_size; ++i) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

現在のレベルのサイズを固定してそこまで回す方法も良いですが、別々のキューを使う方法の方が可読性に優れると感じます。
内側のループを while(!q.empty()) で回し、最後に q = next_q; (または move、swap) とする方法です。

while (!q.empty()) {
    std::queue<TreeNode*> next_q;
    ++depth;

    while (!q.empty()) {
        TreeNode* node = q.front();
        q.pop();
        
        if (node->left != nullptr) {
            next_q.push(node->left);
        }
        if (node->right != nullptr) {
            next_q.push(node->right);
        }
    } 

    q = std::move(next_q);
}

あと、BFS をするのに
node_queue
num_node_in_level
で数を数えて、次のレベルに行くの、少しややこしいと思っています。データの整合性が取れているということは、読んでいる人からすると全部読み終わらないと分からないからです。書いている人は分かるわけですが。
つまり、一つの変数に、2つの違う種類のものを入れておいて、その境界を個数で管理しているわけですよね。

https://discord.com/channels/1084280443945353267/1201211204547383386/1219179255615717399

https://docs.google.com/document/d/11HV35ADPo9QxJOpJQ24FcZvtvioli770WWdZZDaLOfg/edit?tab=t.0#heading=h.ho7q4rvwsa1g

TreeNode* node = q.front();
q.pop();
if (node->left != nullptr) {
q.push(node->left);
}
Comment on lines +128 to +132
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

NULLチェックですが、push前にやる方法のほか、pop直後にやる方法もあります。
q.pop(); の後に、if (node == nullptr) continue; と書くイメージです。上の再帰関数の方法は実質後者のパターンですね。

if (node->right != nullptr) {
q.push(node->right);
}
}
}

return depth;
}
};

```

- トータルで1Qにかかる時間が短くなってきた。

## Step3 *自然な流れ(感覚に落とし込む)
1. 2 min
2. 2 min
2. 1min