Skip to content
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

Add 112. Path Sum.md #25

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
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
188 changes: 188 additions & 0 deletions 112. Path Sum.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
# step 1

再帰なしDFS

ノードの数をnとして、
- time complexity: O(n)
- space complexity: O(n)
```python
class Solution:
def hasPathSum(self, root: Optional[TreeNode], targetSum: int) -> bool:
if root is None:
return False

stack = [(root, root.val)]
while stack:
node, sum_so_far = stack.pop()

Choose a reason for hiding this comment

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

単純にsumでも良いと思いました。

Copy link

Choose a reason for hiding this comment

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

sum は Python では予約語のため、避けたほうがよいと思います。

Choose a reason for hiding this comment

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

失礼いたしました。きちんと調べないとですね。誤ったコメントすみません。

Copy link

Choose a reason for hiding this comment

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

(細かいのと、いろいろな方言があるのですが、予約語は keyword、builtin は組み込みと対応させることが多く、sum は builtin のほうです。)
https://docs.python.org/ja/3/reference/lexical_analysis.html#keywords

Copy link

Choose a reason for hiding this comment

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

失礼いたしました。

Copy link

@olsen-blue olsen-blue Mar 5, 2025

Choose a reason for hiding this comment

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

私は、sum は被るので total にしていました。

if (
node.left is None
and node.right is None
and sum_so_far == targetSum
):
Comment on lines +17 to +21
Copy link

Choose a reason for hiding this comment

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

個人的な好みですが下記のような書き方のほうが読みやすいと思いました、条件部分がカッコ含めてひとかたまりになっていてわかりやすいためです。

            if (node.left is None
                and node.right is None
                and sum_so_far == targetSum):

Copy link
Owner Author

Choose a reason for hiding this comment

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

そうしていない理由としては、最後の条件(今回で言うとand sum_so_far == targetSum):)と条件の下につくstatementが共に4つのスペースが入るため、縦に並べたくないみたいな気持ちがあります。
好みかとは思いますが、こう書こうと思ったのもこの練習を始めてからあまり日が経ってない時なので、提案いただいた書き方も試してみます。

return True
if node.left is not None:
stack.append((node.left, sum_so_far + node.left.val))
if node.right is not None:
stack.append((node.right, sum_so_far + node.right.val))
return False
```

再帰なしDFS(None比較を中に持ってくる)。一つ前に書いたものの方が好みだった。以下の解法だと、stackからpopした段階で、sum_so_farはnodeの値を除くこれまでのpathの総和となっていて少し複雑に感じた。
- time complexity: O(n)
- space complexity: O(n)
```python
class Solution:
def hasPathSum(self, root: Optional[TreeNode], targetSum: int) -> bool:
stack = [(root, 0)]
while stack:
node, sum_so_far = stack.pop()
if node is None:
continue
sum_so_far += node.val
if (
node.left is None
and node.right is None
and sum_so_far == targetSum

Choose a reason for hiding this comment

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

return sum_so_far == targetSumという手もございます🙇

Choose a reason for hiding this comment

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

step2で実装済みでした。失礼しました。

Copy link
Owner Author

Choose a reason for hiding this comment

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

付け加えると、このコードでそれをやってしまうと適当な葉ノードにたどり着いた段階で探索が終わってしまいます。
やるならしたみたいな感じになるかとおもいます。

if node.left is None and node.right is None:
    if sum_so_far == targetSum:
        return True
    continue

):
return True
stack.append((node.left, sum_so_far))
stack.append((node.right, sum_so_far))
return False
```

再帰DFS。木がバランスしていない時に、再帰の深さが最大となり、その際はノードの数と一致する。
left側で見つかってもright側を調べるようにしてしまった。
よって再帰の深さの最大は5000。
- time complexity: O(n)
- space complexity: O(n) (積んでいくスタックのサイズがO(n)となる)
```python
class Solution:
def hasPathSum(self, root: Optional[TreeNode], targetSum: int) -> bool:
if root is None:
return False

def has_path_sum_helper(
node: Optional[TreeNode],
sum_so_far: int
) -> bool:
Copy link

Choose a reason for hiding this comment

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

好みの問題かもしれませんが、カッコ内の改行が多いと読みにくく感じます。2引数程度であれば1行で書いてしまっていいと思います。

if (
node.left is None
and node.right is None
and sum_so_far == targetSum
):
return True

left_path = False
if node.left is not None:
left_path = has_path_sum_helper(
node.left,
sum_so_far + node.left.val
)
right_path = False
if node.right is not None:
right_path = has_path_sum_helper(
node.right,
sum_so_far + node.right.val
)
return left_path or right_path
Copy link

Choose a reason for hiding this comment

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

if node.right is not None:
のブロックの中でhas_path_sum_helperの返り値によってreturnしてしまうこともできそうです。
そうするとこのreturnとleft_path,right_path変数を消去でき、コードをより単純化できると思いました。


return has_path_sum_helper(root, root.val)
```

# step 2
- https://github.com/colorbox/leetcode/pull/39/files
- stackの変数名がstackではなくnodes_and_sums
- c++だと型名にstackが入るのでわかりやすい
- 自分のコードはstackの初期化の2行下で、`node, sum_so_far = stack.pop()`としているので、
何が入ってるか許容範囲内だとは思う。
- https://github.com/olsen-blue/Arai60/pull/25/files
- ヘルパー関数付きのDFS。これまでのpathの和から今注目しているnodeの値を除いたものを引数としていた
- 自分が思っていたよりは複雑さがなかった。
- leftでpathが見つかればすぐにTrueを返すようになっていた
- https://github.com/hroc135/leetcode/pull/24/files#r1806608721
- 短絡評価
- できていなかった
- https://github.com/hayashi-ay/leetcode/pull/30/files
- targetSumから引いていくようにすれば、helperなしでも再帰でかける

targetSumを引いていきながら、そのまま再帰

Choose a reason for hiding this comment

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

好みですが、私は targetSum は名前的に固定値のままにしたい派です。
olsen-blue/Arai60#25 (comment)

```python
class Solution:
def hasPathSum(self, root: Optional[TreeNode], targetSum: int) -> bool:
if root is None:
return False
if root.left is None and root.right is None:
return root.val == targetSum
return (
self.hasPathSum(root.left, targetSum - root.val)
or self.hasPathSum(root.right, targetSum - root.val)
)
```

Noneぶっ込み、短絡評価
```python
class Solution:
def hasPathSum(self, root: Optional[TreeNode], targetSum: int) -> bool:
def has_path_sum_helper(
node: Optional[TreeNode],
sum_so_far: int
) -> bool:
if node is None:
return False
sum_so_far += node.val
if node.left is None and node.right is None:
return sum_so_far == targetSum
return (
has_path_sum_helper(node.left, sum_so_far)
or has_path_sum_helper(node.right, sum_so_far)
)

return has_path_sum_helper(root, 0)
```

Noneぶっ込み、stack使用解法
```python
class Solution:
def hasPathSum(self, root: Optional[TreeNode], targetSum: int) -> bool:
nodes_and_sums = [(root, 0)]
while nodes_and_sums:
node, sum_so_far = nodes_and_sums.pop()
if node is None:
continue
sum_so_far += node.val
if node.left is None and node.right is None:
if sum_so_far == targetSum:
return True
continue
Copy link

Choose a reason for hiding this comment

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

スタックに空ノードが入ることを許容する方法のメリットはループ内の条件分岐が減ることにあると思うので、私は単に

if node.left is None and node.right is None and sum_so_far == targetSum:
    return True

とします。

nodes_and_sums.append((node.left, sum_so_far))
nodes_and_sums.append((node.right, sum_so_far))
return False
```

# step 3

結局,sum_so_farという値が自分の中で一番整合性のとれている再帰を使わない解法を書いた。

```python
class Solution:
def hasPathSum(self, root: Optional[TreeNode], targetSum: int) -> bool:
if root is None:
return False

nodes_and_sums = [(root, root.val)]
while nodes_and_sums:
node, sum_so_far = nodes_and_sums.pop()
if (
node.left is None
and node.right is None
and sum_so_far == targetSum
):
return True
if node.left is not None:
nodes_and_sums.append((node.left, sum_so_far + node.left.val))
if node.right is not None:
nodes_and_sums.append((node.right, sum_so_far + node.right.val))
return False
```

雑な感想)成長かはわからないが、プログラムの中で空行を入れる箇所が減った気がする。
Copy link

Choose a reason for hiding this comment

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

成長かと思います。

それはそうと、複数行の文の改行の入れ方、フォーマッターに一度かけてみるといいかもしれません。
別に、絶対的な標準があるわけではなくいろいろな流儀があり、どれが正しいというわけではないですが、参考になるものもあるでしょう。

Copy link
Owner Author

Choose a reason for hiding this comment

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

フォーマッターというとblackとかですかね。使って変化を見てみます。

あと、少し話がそれますが、leetcodeだとtype annotationが全体的に古いことが気になります。leetcodeでのpython3のバージョンは3.11みたいなので(https://support.leetcode.com/hc/en-us/articles/360011833974-What-are-the-environments-for-the-programming-languages)、[List](https://docs.python.org/3/library/typing.html#typing.List)とかdeprecatedなように思います。
こういう状況だと、既存のものもゴリゴリ書き換えていくのがいいのか、周りの雰囲気に合わせて自分もその様式に倣うのか、自分の新たに書くものだけはとりあえず新しいものを使うのか(今回だったらinner functionのアノテーションは新しくするとか)って、状況次第なかんじなんでしょうか?動くコードが正義だから書き換えないみたいな主張もあると思いますが、実務でどのようなバランスを取るのか気になりました。

Choose a reason for hiding this comment

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

これわかる気がします。
最初の慣れていないときは空行で機能の違うものはとりあえず分離して、安心したい感情がありました。

Copy link

Choose a reason for hiding this comment

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

私もなぜか空行が減りました。以前はコードの可読性を高める手段として変数名の工夫と空行を挟むことくらいしか持ち合わせていなかったのでそうしていたような気がします。

Copy link

Choose a reason for hiding this comment

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

コードを読む機会が増えて空行がないコードを読むことが苦にならなくなったのもあると思います。