-
Notifications
You must be signed in to change notification settings - Fork 0
111. Minimum Depth of Binary Tree #21
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,240 @@ | ||
| # 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<TreeNode> 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; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. !treeNodes.isEmpty になることがないはずなので、無限ループにしてこの行消したほうがいいのかなと思いました。 |
||
| } | ||
| } | ||
| ``` | ||
|
|
||
| 次のようなことを考えながら実装していました: | ||
|
|
||
| - 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 することも考えたが、後続処理にどのような影響を与えるべきかの議論なしにそこまでやるのは憚られたのでこのようにした | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 自分としてはException を throwすることは憚られるが-1を返すのは良いという理屈があまり分かりませんでした。
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 別のスレッドに返信していたので一応こちらにもリンク貼っておきます: #21 (comment) |
||
| - 以前同じような議論がされていたので参考まで: 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<TreeNodeDepth> 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) { | ||
| 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)); | ||
| } | ||
|
Comment on lines
+86
to
+91
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 細かいですが、stack-dfsは右>左の順に書いたほうが自然な処理の流れで好みです。 |
||
| } | ||
| return minDepth; | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ### 再帰関数による DFS | ||
|
|
||
| ```java | ||
| // 時間計算量: O(n) | ||
| // 空間計算量: O(n) | ||
| class Solution { | ||
| public int minDepth(TreeNode root) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. minDepth自体を再帰させることもできそうですか?
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. はい、次のように出来ます class Solution {
public int minDepth(TreeNode root) {
if (root == null) {
return 0;
}
if (root.left == null && root.right == null) {
return 1;
}
int leftNodeMinDepth = Integer.MAX_VALUE;
if (root.left != null) {
leftNodeMinDepth = minDepth(root.left);
}
int rightNodeMinDepth = Integer.MAX_VALUE;
if (root.right != null) {
rightNodeMinDepth = minDepth(root.right);
}
return Math.min(leftNodeMinDepth, rightNodeMinDepth) + 1;
}
} |
||
| if (root == null) { | ||
| return 0; | ||
| } | ||
|
|
||
| return traverseMinDepthRecursively(root); | ||
| } | ||
|
|
||
| private int traverseMinDepthRecursively(TreeNode node) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. traverse は他動詞としては https://eow.alc.co.jp/search?q=traverse
という意味があり、目的語を min depth にすると意味が通らなくなってしまうように思います。別の表現は思いつきますか?
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @nodchip
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. はい、よいと思います。
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ありがとうございます! |
||
| if (node.left == null && node.right == null) { | ||
| return 1; | ||
| } | ||
|
|
||
| int leftNodeMinDepth = Integer.MAX_VALUE; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. とすると、少しシンプルになると思います。
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ありがとうございます!Step 4 に書いておきます。 |
||
| 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; | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ## 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<TreeNode> 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; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
期待されない値というか、ここって到達しないので、throw Exception("unreachable")が正しいんじゃないでしょうか?? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 自分もここは気になりました。アルゴリズム上到達しない箇所にreturnが書いてあったらどこかで実装ミスをしたのかな、と思って混乱するかもなと思いました
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 到達しないつもりで書いてはいるのですが、コンパイラによってチェックされているといった保証が無いので、到達する可能性は排除出来ない認識でいます。 public class Driver {
public static void main(String[] args) {
TreeNode root = /* 木の構築 */;
Solution solution = new Solution();
OtherLogics logics = new OtherLogics();
try {
int minDepth = solution.minDepth(root);
int valCanBeTrivial = logics.somethingTrivial(minDepth);
logics.somethingVeryImportantMustBeReached(valCanBeTrivial);
} catch (Exception e) {
System.err.println("エラーが発生しました: " + e.getMessage());
}
}
}特に確認が出来ていない状況であれば、とりあえず選ぶならば、後続処理を止める可能性のある実装より、そうでない実装の方にした方がまだいいのかなと考えていました。
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. とは言うものの、そもそもチェック例外のある関数を呼ぶなら呼ぶ側が気をつければいいという話ではあるかもしれないですね... と上記書いてて思いました。 public class Driver {
public static void main(String[] args) {
TreeNode root = /* 木の構築 */;
Solution solution = new Solution();
OtherLogics logics = new OtherLogics();
int valCanBeTrivial = 0; // デフォルト値
try {
int minDepth = solution.minDepth(root);
valCanBeTrivial = logics.somethingTrivial(minDepth);
} catch (Exception e) {
System.err.println("エラーが発生しました: " + e.getMessage());
} finally { // 👈 例外の発生有無に関わらずこのブロック内の処理は必ず実行される
logics.somethingVeryImportantMustBeReached(valCanBeTrivial);
}
}
}There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ちなみに到達する場合って具体的にどういうケースでしょうか。 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 私がよく分かっていない可能性があるのですが、
とあり、どのような入力を与えると(若しくは事故?が起こると) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
なるほどありがとうございます。まあ例外を避けたいという気持ちも分かりますし、returnにするのもあるかもなという気持ちになりました
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
すいません、返信がかなり遅くなってしまいました。
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. これだけ多くの人が見ても見つからないのでこの関数に関してはもうこの行に到達することはないと考えていいと思います。ただそれはそれとして、システムによる保証がない状況で強いて選ぶなら... と考えてこのようにした次第です。 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. あ、これなんですが、(別のスレッドからこの変数をどうにかしていじるなどがなければ)到達しないと思うのですが「コンパイラが到達しないことを理解してくれるか」は別問題です。一般に、一重ループの停止性問題は決定不能なので、コンパイラが停止するかしないかを100%の精度で当てることはありません。 |
||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ## 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<TreeNode> 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; | ||
| } | ||
| } | ||
| ``` | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
calculate には、計算式を用いて何らかの計算をするという意味合いがあるように思います。このプログラムは特に計算をするわけではないため、 calculate という単語を使うのは不適切なように思います。別の単語を使うことをお勧めいたします。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@nodchip
ありがとうございます。確かにそうですね。
MIN_DEPTH_NOT_FOUNDが適切なのかなと考えましたが、こちらはいかがでしょうか?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
はい、そちらのほうが良いと思います。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ありがとうございます!こちら Step 4 の方に書いておきます。