From cc353cdde9e0230b2988e33db1ed44d9d353be76 Mon Sep 17 00:00:00 2001 From: seal_azarashi Date: Tue, 10 Sep 2024 14:44:50 +0900 Subject: [PATCH 1/5] step 1, 2 --- .../minimum-depth-of-binary-tree.md | 129 ++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 arai60/Tree_BT_BST/minimum-depth-of-binary-tree.md diff --git a/arai60/Tree_BT_BST/minimum-depth-of-binary-tree.md b/arai60/Tree_BT_BST/minimum-depth-of-binary-tree.md new file mode 100644 index 0000000..89b0e87 --- /dev/null +++ b/arai60/Tree_BT_BST/minimum-depth-of-binary-tree.md @@ -0,0 +1,129 @@ +# 111. Minimum Depth of Binary Tree + +LeetCode URL: https://leetcode.com/problems/minimum-depth-of-binary-tree/description/ + +この問題は Java で解いています。 +各解法において、メソッドが属するクラスとして `Solution` を定義していますが、これは Java の言語仕様に従い、コードを実行可能にするために必要なものです。このクラス自体には特定の意味はなく、単にメソッドを組織化し、実行可能にするためのものです。 + +## Step 1 + +```java +// 解いた時間: 5分ぐらい +// 時間計算量: O(n) +// 空間計算量: O(n) +class Solution { + private static final int MIN_DEPTH_NOT_CALCULATED = -1; + + public int minDepth(TreeNode root) { + if (root == null) { + return 0; + } + + Queue treeNodes = new ArrayDeque<>(); + treeNodes.offer(root); + int depth = 1; + while (!treeNodes.isEmpty()) { + int currentLevelNodesCount = treeNodes.size(); + for (int i = 0; i < currentLevelNodesCount; i++) { + TreeNode node = treeNodes.poll(); + if (node.left == null && node.right == null) { + return depth; + } + if (node.left != null) { + treeNodes.offer(node.left); + } + if (node.right != null) { + treeNodes.offer(node.right); + } + } + + depth++; + } + + // 処理に異常が無い限りここには到達しません。 + return MIN_DEPTH_NOT_CALCULATED; + } +} +``` + +次のようなことを考えながら実装していました: + +- Queue を用いた BFS と再帰関数、スタックによる DFS の計3つのアプローチを思いつく +- 1つめのアプローチはスタックオーバーフローのリスクが無く、リーフノードを見つけた時点で処理が終了できて効率がいい、また実装も他2つと比べ理解しやすい内容になると考え、そちらで書いてみた + - Leetcode の constraints 上はスタックオーバーフローのリスクはほぼ無いと考えらるが、 [oda さんのコメント](https://github.com/kazukiii/leetcode/pull/22/files#r1667746480)にある通り「この環境では大丈夫」なコードはいつ自分の足を撃ち抜いて来るかわからないので避けた +- 次の理由でキュー (ArrayDeque) へは null を挿入していない: + - そもそも ArrayDeque の各メソッドは null を挿入することを許容しない + - null チェックをイテレーションごとに都度実施しなくていいなら、そうする越したことはないと思う + - [ArrayDeque はドキュメント冒頭にある通り早い](https://docs.oracle.com/en%2Fjava%2Fjavase%2F22%2Fdocs%2Fapi%2F%2F/java.base/java/util/ArrayDeque.html)ので、その利点を捨ててまで、例えば null 許容する LinkedList を implementing class にすることもないかなと考える +- minDepth() の最下部は次のようなことを考えて実装した: + - 処理に異常がない限りは到達しないことを明記 + - Depth の値として期待されない -1 を返す代わりに Exception を throw することも考えたが、後続処理にどのような影響を与えるべきかの議論なしにそこまでやるのは憚られたのでこのようにした + - 以前同じような議論がされていたので参考まで: https://github.com/seal-azarashi/leetcode/pull/9/files#r1667365546 + +## Step 2 + +### 再帰関数による DFS + +```java +// 時間計算量: O(n) +// 空間計算量: O(n) +class Solution { + private record TreeNodeDepth(TreeNode node, int depth) {}; + + public int minDepth(TreeNode root) { + if (root == null) { + return 0; + } + + Deque treeNodeDepths = new ArrayDeque<>(); + treeNodeDepths.push(new TreeNodeDepth(root, 1)); + int minDepth = Integer.MAX_VALUE; + while (!treeNodeDepths.isEmpty()) { + TreeNodeDepth nodeDepth = treeNodeDepths.pop(); + if (nodeDepth.node.left == null && nodeDepth.node.right == null) { + System.out.println(nodeDepth.node.val); + minDepth = Math.min(minDepth, nodeDepth.depth); + } + if (nodeDepth.node.left != null) { + treeNodeDepths.push(new TreeNodeDepth(nodeDepth.node.left, nodeDepth.depth + 1)); + } + if (nodeDepth.node.right != null) { + treeNodeDepths.push(new TreeNodeDepth(nodeDepth.node.right, nodeDepth.depth + 1)); + } + } + return minDepth; + } +} +``` + +### スタックによる DFS + +```java +// 時間計算量: O(n) +// 空間計算量: O(n) +class Solution { + public int minDepth(TreeNode root) { + if (root == null) { + return 0; + } + + return traverseMinDepthRecursively(root); + } + + private int traverseMinDepthRecursively(TreeNode node) { + if (node.left == null && node.right == null) { + return 1; + } + + int leftNodeMinDepth = Integer.MAX_VALUE; + if (node.left != null) { + leftNodeMinDepth = traverseMinDepthRecursively(node.left); + } + int rightNodeMinDepth = Integer.MAX_VALUE; + if (node.right != null) { + rightNodeMinDepth = traverseMinDepthRecursively(node.right); + } + return Math.min(leftNodeMinDepth, rightNodeMinDepth) + 1; + } +} +``` From 536befbb68054ba8a03b85ca021e98f6873d8ffd Mon Sep 17 00:00:00 2001 From: seal_azarashi Date: Tue, 10 Sep 2024 15:55:59 +0900 Subject: [PATCH 2/5] step 3 --- .../minimum-depth-of-binary-tree.md | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/arai60/Tree_BT_BST/minimum-depth-of-binary-tree.md b/arai60/Tree_BT_BST/minimum-depth-of-binary-tree.md index 89b0e87..6e57c9e 100644 --- a/arai60/Tree_BT_BST/minimum-depth-of-binary-tree.md +++ b/arai60/Tree_BT_BST/minimum-depth-of-binary-tree.md @@ -127,3 +127,42 @@ class Solution { } } ``` + +## Step 3 + +```java +// 解いた時間: 5分ぐらい +// 時間計算量: O(n) +// 空間計算量: O(n) +class Solution { + private static final int MIN_DEPTH_NOT_CALCULATED = -1; + + public int minDepth(TreeNode root) { + if (root == null) { + return 0; + } + + Queue treeNodes = new ArrayDeque<>(); + treeNodes.offer(root); + int minDepth = 1; + while (!treeNodes.isEmpty()) { + int currentLevelNodeCount = treeNodes.size(); + for (int i = 0; i < currentLevelNodeCount; i++) { + TreeNode node = treeNodes.poll(); + if (node.left == null && node.right == null) { + return minDepth; + } + if (node.left != null) { + treeNodes.offer(node.left); + } + if (node.right != null) { + treeNodes.offer(node.right); + } + } + minDepth++; + } + + return MIN_DEPTH_NOT_CALCULATED; + } +} +``` From c5bc802888afa347b2fcfdc13cd0c62cf5ec75e3 Mon Sep 17 00:00:00 2001 From: seal_azarashi Date: Wed, 11 Sep 2024 07:13:51 +0900 Subject: [PATCH 3/5] delete print --- arai60/Tree_BT_BST/minimum-depth-of-binary-tree.md | 1 - 1 file changed, 1 deletion(-) diff --git a/arai60/Tree_BT_BST/minimum-depth-of-binary-tree.md b/arai60/Tree_BT_BST/minimum-depth-of-binary-tree.md index 6e57c9e..2bad096 100644 --- a/arai60/Tree_BT_BST/minimum-depth-of-binary-tree.md +++ b/arai60/Tree_BT_BST/minimum-depth-of-binary-tree.md @@ -81,7 +81,6 @@ class Solution { while (!treeNodeDepths.isEmpty()) { TreeNodeDepth nodeDepth = treeNodeDepths.pop(); if (nodeDepth.node.left == null && nodeDepth.node.right == null) { - System.out.println(nodeDepth.node.val); minDepth = Math.min(minDepth, nodeDepth.depth); } if (nodeDepth.node.left != null) { From aa47297b61ce6d86a58993a2ccac5657dc0d94a9 Mon Sep 17 00:00:00 2001 From: seal_azarashi Date: Wed, 11 Sep 2024 07:36:57 +0900 Subject: [PATCH 4/5] fix --- arai60/Tree_BT_BST/minimum-depth-of-binary-tree.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arai60/Tree_BT_BST/minimum-depth-of-binary-tree.md b/arai60/Tree_BT_BST/minimum-depth-of-binary-tree.md index 2bad096..fd7a0f8 100644 --- a/arai60/Tree_BT_BST/minimum-depth-of-binary-tree.md +++ b/arai60/Tree_BT_BST/minimum-depth-of-binary-tree.md @@ -62,7 +62,7 @@ class Solution { ## Step 2 -### 再帰関数による DFS +### スタックによる DFS ```java // 時間計算量: O(n) @@ -95,7 +95,7 @@ class Solution { } ``` -### スタックによる DFS +### 再帰関数による DFS ```java // 時間計算量: O(n) From 9ee891d8b32a55659f145fc7fe899c627071a074 Mon Sep 17 00:00:00 2001 From: seal_azarashi Date: Wed, 11 Sep 2024 09:19:22 +0900 Subject: [PATCH 5/5] step 4 --- .../minimum-depth-of-binary-tree.md | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/arai60/Tree_BT_BST/minimum-depth-of-binary-tree.md b/arai60/Tree_BT_BST/minimum-depth-of-binary-tree.md index fd7a0f8..5006618 100644 --- a/arai60/Tree_BT_BST/minimum-depth-of-binary-tree.md +++ b/arai60/Tree_BT_BST/minimum-depth-of-binary-tree.md @@ -165,3 +165,76 @@ class Solution { } } ``` + +## Step 4 + +### キューによる BFS + +```java +// 時間計算量: O(n) +// 空間計算量: O(n) +class Solution { + private static final int MIN_DEPTH_NOT_FOUND = -1; + + public int minDepth(TreeNode root) { + if (root == null) { + return 0; + } + + Queue treeNodes = new ArrayDeque<>(); + treeNodes.offer(root); + int depth = 1; + while (!treeNodes.isEmpty()) { + int currentLevelNodesCount = treeNodes.size(); + for (int i = 0; i < currentLevelNodesCount; i++) { + TreeNode node = treeNodes.poll(); + if (node.left == null && node.right == null) { + return depth; + } + if (node.left != null) { + treeNodes.offer(node.left); + } + if (node.right != null) { + treeNodes.offer(node.right); + } + } + + depth++; + } + + // 処理に異常が無い限りここには到達しません。 + return MIN_DEPTH_NOT_FOUND; + } +} +``` + +### 再帰関数による DFS + +```java +// 時間計算量: O(n) +// 空間計算量: O(n) +class Solution { + public int minDepth(TreeNode root) { + if (root == null) { + return 0; + } + + return findMinDepthRecursively(root); + } + + private int findMinDepthRecursively(TreeNode node) { + if (node.left == null && node.right == null) { + return 1; + } + + int minDepth = Integer.MAX_VALUE; + if (node.left != null) { + minDepth = Math.min(minDepth , findMinDepthRecursively(node.left)); + } + if (node.right != null) { + minDepth = Math.min(minDepth , findMinDepthRecursively(node.right)); + } + return minDepth + 1; + } +} +```