-
Notifications
You must be signed in to change notification settings - Fork 0
Create 0112-path-sum.md #29
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,84 @@ | ||
| ## Step 1 | ||
|
|
||
| - 問題文 | ||
| - 二分木の根`root`と整数`targetSum`が与えられる。 | ||
| - 根から葉までの経路で、valueを足し上げると`targetSum`に等しくなるような経路があれば`True`を返す。 | ||
| - なお葉とは、子のないノードである。 | ||
| - 制約: | ||
| - The number of nodes in the tree is in the range [0, 5000]. | ||
| - -1000 <= Node.val <= 1000 | ||
| - -1000 <= targetSum <= 1000 | ||
|
|
||
| ### 実装1 | ||
|
|
||
| - アルゴリズムの選択 | ||
| - BFS/DFSであらゆる経路について素直に葉まで足し合わせ、葉でtargetSumになればreturn Trueすれば良さそう。 | ||
| - node.valは正も負も取りうることから、足し合わせていく和は単調に変動しないので、targetSum以上(以下)になった時点で打ち切りする、などの枝刈りはできないと判断。 | ||
| - 実装 | ||
| - whileループで探索する。 | ||
| - キューに入れる前にnodeがNoneでないかを弾くと見通しが良いので、そうする。 | ||
| - 最初にroot is Noneを弾いておく必要がある。今回は忘れずにできた。 | ||
| - 計算量 | ||
| - Time: O(V + E) = O(V) | ||
| - Space: O(width) = O(V) | ||
|
|
||
| ```python3 | ||
| from typing import Optional | ||
| from collections import deque | ||
|
|
||
|
|
||
| # Definition for a binary tree node. | ||
| # class TreeNode: | ||
| # def __init__(self, val=0, left=None, right=None): | ||
| # self.val = val | ||
| # self.left = left | ||
| # self.right = right | ||
| class Solution: | ||
| def hasPathSum(self, root: Optional[TreeNode], targetSum: int) -> bool: | ||
| if root is None: | ||
| return False | ||
|
|
||
| frontiers = deque() | ||
|
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. その感覚わかります、"queue"が標準モジュールにあるので、語感が好きなfrontiersを第一候補にしがちです。 なるほど、そうなんですね。 |
||
| frontiers.append((root, 0)) | ||
| while frontiers: | ||
| node, total = frontiers.popleft() | ||
| total += node.val | ||
| if node.left is None and node.right is None: | ||
| if total == targetSum: | ||
| return True | ||
| continue | ||
| if node.left is not None: | ||
| frontiers.append((node.left, total)) | ||
| if node.right is not None: | ||
| frontiers.append((node.right, total)) | ||
| return False | ||
| ``` | ||
|
|
||
| ここまで5分。 | ||
|
|
||
| ## Step 2 | ||
|
|
||
| - GPT-5によるレビュー | ||
| - > 現在は取り出し後に total += node.val。読みやすさを優先するなら、キューには「次に検査する時点での合計」を積むか、 | ||
| - 積む時点で加算する場合、足し算の位置がコード中で分散してしまうのが好みでない。 | ||
| - > あるいは subtractive 形式で「残り」を積むと、葉の判定が1行で済みます。 | ||
| - これは思いつかなかった。しかし、差に注目するのは不必要にロジックを複雑化させているように感じてしまう。 | ||
|
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. 再帰の場合は引数のtargetSumを減らして呼ぶのが自然なので発想として出てきますが、whileで書く場合はスタックに積むか親の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. ありがとうございます。
これは気づきませんでした、その通りですね。
そうなんですよね。個人的に最近はなるべくiterativeに書くようにしているのもあり。 |
||
| - [コメント集](https://docs.google.com/document/d/11HV35ADPo9QxJOpJQ24FcZvtvioli770WWdZZDaLOfg/edit?tab=t.0#heading=h.ed3x3pkyeqkp) | ||
| - https://discord.com/channels/1084280443945353267/1225849404037009609/1258455843226255361 | ||
| - > これ、引き算先にしちゃって、 | ||
| ```python3 | ||
| if not node.left and not node.right: | ||
| return rest == 0 | ||
| ``` | ||
| > のほうが素直ではないでしょうか | ||
| - 最初から同じ感覚で書けたのでよかった。 | ||
| - 葉を先に例外的に処理し、その下で通常の子を持つノードを処理する。 | ||
| - https://discord.com/channels/1084280443945353267/1228700203327164487/1229111777946505216 | ||
| - > いわゆるスタックを使って書き直せますか?スタックは ArrayDeque でしたっけ。Stack は、もう使わないんでしたっけ。 | ||
| - pythonなら.popleft()を.pop()にするだけなので簡単。 | ||
| - javaでArrayDequeが推奨されていることは自分も以前のレビューで言及したことがある。 | ||
| - https://discordapp.com/channels/1084280443945353267/1402285228722229258/1426823682239631420 | ||
|
|
||
| ## Step 3 | ||
|
|
||
| [実装1](#実装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.
もし葉でtargetSumになるような経路を返却するとしたらどうでしょうか?
設問としてはTrue/Falseなのですが、単純にTrue/Falseを得るよりはpathを実際に知るほうが意味があるのかなと思ったので...自分が面接だったら質問しそうです。
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.
follow-upありがとうございます。
そうですね、通ったノードのその時点の和を記録するsetまたは辞書を持つようにし、ゴールの葉からスタートに向かって再びBFS/DFSするのが(空間)計算量が節約できて良さそうですね。
さらに、そのようなpathの総数/pathを全部知りたい場合は、1回目の前向きの探索をearly returnなしで完遂し、和の記録も通った回数を辞書で記録しておくのが良さそうに思います。