-
Notifications
You must be signed in to change notification settings - Fork 0
112. Path Sum #24
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?
112. Path Sum #24
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,225 @@ | ||
| # 112. Path Sum | ||
|
|
||
| LeetCode URL: https://leetcode.com/problems/path-sum/description/ | ||
|
|
||
| この問題は Java で解いています。 | ||
| 各解法において、メソッドが属するクラスとして `Solution` を定義していますが、これは Java の言語仕様に従い、コードを実行可能にするために必要なものです。このクラス自体には特定の意味はなく、単にメソッドを組織化し、実行可能にするためのものです。 | ||
|
|
||
| ## Step 1 | ||
|
|
||
| ```java | ||
| // 解いた時間: 7分ぐらい | ||
| // 時間計算量: O(n): 最大で全ノードを走査する | ||
| // 空間計算量: O(n): 最大で全ノードがスタックに入る | ||
| class Solution { | ||
| private record NodeWithPathSum(TreeNode node, int pathSum) {}; | ||
|
|
||
| public boolean hasPathSum(TreeNode root, int targetSum) { | ||
| if (root == null) { | ||
| return false; | ||
| } | ||
|
|
||
| Deque<NodeWithPathSum> nodeStack = new ArrayDeque<>(); | ||
| nodeStack.push(new NodeWithPathSum(root, 0)); | ||
| while (!nodeStack.isEmpty()) { | ||
| NodeWithPathSum nodeWithPathSum = nodeStack.pop(); | ||
| TreeNode node = nodeWithPathSum.node; | ||
| int currentPathSum = node.val + nodeWithPathSum.pathSum; | ||
|
|
||
| boolean isLeafNode = node.left == null && node.right == null; | ||
| if (isLeafNode && currentPathSum == targetSum) { | ||
| return true; | ||
| } | ||
|
|
||
| if (node.right != null) { | ||
| nodeStack.push(new NodeWithPathSum(node.right, currentPathSum)); | ||
| } | ||
| if (node.left != null) { | ||
| nodeStack.push(new NodeWithPathSum(node.left, currentPathSum)); | ||
| } | ||
| } | ||
|
|
||
| return false; | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| 次のようなことを考えながら実装していました: | ||
|
|
||
| - DFS アプローチで解くのが適切に思える | ||
| - もっと効率よく出来ないか考えるも、全ノードを順にリーフノードまで見ていくのは避けられないと判断 | ||
| - 値が0以上しか入り得ないなら途中で打ち切る処理も書けるが、負の数が入り得るのは constraints にも明記されてる | ||
| - 明記されてなくても、面接官に合意を取らない限りは打ち切るような処理は勝手に入れないほうがよさそう | ||
| - 値が0以上であるかどうかについては型システムによって保証されてるわけでもないので | ||
| - スタックオーバーフローのリスクを回避するためスタックで実装したい | ||
|
|
||
| ## Step 2 | ||
|
|
||
| ### 再帰で DFS | ||
|
|
||
| スタックオーバーフローのリスクがあるので最適解にはならないと思いますが、一応練習も兼ねて書いてみます。 | ||
|
|
||
| ```java | ||
| // 時間計算量: O(n): 最大で全ノードを走査する | ||
| // 空間計算量: O(n): 最大で全ノードがスタックフレームに積まれる | ||
| class Solution { | ||
| public boolean hasPathSum(TreeNode root, int targetSum) { | ||
| if (root == null) { | ||
| return false; | ||
| } | ||
| boolean isLeafNode = root.left == null && root.right == null; | ||
| if (isLeafNode && root.val == targetSum) { | ||
| return true; | ||
| } | ||
|
Comment on lines
+71
to
+73
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 (isLeafNode) {
return root.val == targetSum;
}
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 に修正版を書きました |
||
| return | ||
| hasPathSum(root.left, targetSum - root.val) || | ||
| hasPathSum(root.right, targetSum - root.val); | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ## Step 3 | ||
|
|
||
| Step 1 と同じです。 | ||
|
|
||
| ```java | ||
| // 解いた時間: 5分ぐらい | ||
| // 時間計算量: O(n): 最大で全ノードを走査する | ||
| // 空間計算量: O(n): 最大で全ノードがスタックに入る | ||
| class Solution { | ||
| private record NodeWithPathSum(TreeNode node, int pathSum) {}; | ||
|
|
||
| public boolean hasPathSum(TreeNode root, int targetSum) { | ||
| if (root == null) { | ||
| return false; | ||
| } | ||
|
|
||
| Deque<NodeWithPathSum> nodeStack = new ArrayDeque<>(); | ||
| nodeStack.push(new NodeWithPathSum(root, 0)); | ||
| while (!nodeStack.isEmpty()) { | ||
| NodeWithPathSum nodeWithPathSum = nodeStack.pop(); | ||
| TreeNode node = nodeWithPathSum.node; | ||
| int currentPathSum = node.val + nodeWithPathSum.pathSum; | ||
|
|
||
| boolean isLeafNode = node.left == null && node.right == null; | ||
| if (isLeafNode && currentPathSum == targetSum) { | ||
| return true; | ||
| } | ||
|
Comment on lines
+105
to
+107
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 (isLeafNode) {
if (currentPathSum == targetSum) {
return true;
}
continue;
}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.right != null) { | ||
| nodeStack.push(new NodeWithPathSum(node.right, currentPathSum)); | ||
| } | ||
| if (node.left != null) { | ||
| nodeStack.push(new NodeWithPathSum(node.left, currentPathSum)); | ||
| } | ||
| } | ||
|
|
||
| return false; | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ## Step 4 | ||
|
|
||
| ### イテレーティブな DFS | ||
|
|
||
| 次の指摘に対応: | ||
|
|
||
| - https://github.com/seal-azarashi/leetcode/pull/24#discussion_r1781228402 | ||
|
|
||
| ```java | ||
| // 時間計算量: O(n): 最大で全ノードを走査する | ||
| // 空間計算量: O(n): 最大で全ノードがスタックに入る | ||
| class Solution { | ||
| private record NodeWithPathSum(TreeNode node, int pathSum) {}; | ||
|
|
||
| public boolean hasPathSum(TreeNode root, int targetSum) { | ||
| if (root == null) { | ||
| return false; | ||
| } | ||
|
|
||
| Deque<NodeWithPathSum> nodeStack = new ArrayDeque<>(); | ||
| nodeStack.push(new NodeWithPathSum(root, 0)); | ||
| while (!nodeStack.isEmpty()) { | ||
| NodeWithPathSum nodeWithPathSum = nodeStack.pop(); | ||
| TreeNode node = nodeWithPathSum.node; | ||
| int currentPathSum = node.val + nodeWithPathSum.pathSum; | ||
|
|
||
| if (node.left == null && node.right == null) { | ||
| if (currentPathSum == targetSum) { | ||
| return true; | ||
| } | ||
|
|
||
| continue; | ||
| } | ||
|
|
||
| if (node.right != null) { | ||
| nodeStack.push(new NodeWithPathSum(node.right, currentPathSum)); | ||
|
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. Java は最近(Tiger 以降)ほとんど書いていないのですが、record は null 入らないんでしたっけ。スタックには null も入るようにして、チェックを pop してから行うのも一つかと思いました。
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. あれ、ArrayDeque は、null が入らないのはいいですが、null を要素に持つ Record も入らないということですか?
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. あ、すいません読み違えてました。Null であるフィールドを持つ record は許容されます (null だとだめなのは record のインスタンスでした)。ですので、スタックには null も入るようにして、チェックを pop してから行うのは可能です。 こんな実装になります (step 4 に記載しました) class Solution {
private record NodeWithPathSum(TreeNode node, int pathSum) {};
public boolean hasPathSum(TreeNode root, int targetSum) {
if (root == null) {
return false;
}
Deque<NodeWithPathSum> nodeStack = new ArrayDeque<>();
nodeStack.push(new NodeWithPathSum(root, 0));
while (!nodeStack.isEmpty()) {
NodeWithPathSum nodeWithPathSum = nodeStack.pop();
TreeNode node = nodeWithPathSum.node;
if (node == null) {
continue;
}
int currentPathSum = node.val + nodeWithPathSum.pathSum;
if (node.left == null && node.right == null) {
if (currentPathSum == targetSum) {
return true;
}
continue;
}
nodeStack.push(new NodeWithPathSum(node.right, currentPathSum));
nodeStack.push(new NodeWithPathSum(node.left, currentPathSum));
}
return false;
}
}
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) { | ||
| nodeStack.push(new NodeWithPathSum(node.left, currentPathSum)); | ||
| } | ||
| } | ||
| return false; | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| スタックには null も入るようにして、チェックを pop してから行うパターン (参考: https://github.com/seal-azarashi/leetcode/pull/24#discussion_r1781230995) | ||
|
|
||
| ```java | ||
| // 時間計算量: O(n): 最大で全ノードを走査する | ||
| // 空間計算量: O(n): 最大で全ノードがスタックに入る | ||
| class Solution { | ||
| private record NodeWithPathSum(TreeNode node, int pathSum) {}; | ||
|
|
||
| public boolean hasPathSum(TreeNode root, int targetSum) { | ||
| if (root == null) { | ||
| return false; | ||
| } | ||
|
|
||
| Deque<NodeWithPathSum> nodeStack = new ArrayDeque<>(); | ||
| nodeStack.push(new NodeWithPathSum(root, 0)); | ||
| while (!nodeStack.isEmpty()) { | ||
| NodeWithPathSum nodeWithPathSum = nodeStack.pop(); | ||
| TreeNode node = nodeWithPathSum.node; | ||
|
|
||
| if (node == null) { | ||
| continue; | ||
| } | ||
|
|
||
| int currentPathSum = node.val + nodeWithPathSum.pathSum; | ||
| if (node.left == null && node.right == null) { | ||
| if (currentPathSum == targetSum) { | ||
| return true; | ||
| } | ||
|
|
||
| continue; | ||
| } | ||
| nodeStack.push(new NodeWithPathSum(node.right, currentPathSum)); | ||
| nodeStack.push(new NodeWithPathSum(node.left, currentPathSum)); | ||
| } | ||
| return false; | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ### 再帰で DFS | ||
|
|
||
| ```java | ||
| // 時間計算量: O(n): 最大で全ノードを走査する | ||
| // 空間計算量: O(n): 最大で全ノードがスタックフレームに積まれる | ||
| class Solution { | ||
| public boolean hasPathSum(TreeNode root, int targetSum) { | ||
| if (root == null) { | ||
| return false; | ||
| } | ||
| if (root.left == null && root.right == null) { | ||
| return root.val == targetSum; | ||
| } | ||
| return | ||
| hasPathSum(root.left, targetSum - root.val) || | ||
| hasPathSum(root.right, targetSum - root.val); | ||
| } | ||
| } | ||
| ``` | ||
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 を修正しました。