Skip to content
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
195 changes: 195 additions & 0 deletions easy/112/answer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
# Step1

かかった時間:13min

計算量:
ノードの数をNとして

時間計算量:O(N)

空間計算量:O(N)

再帰
```python
# 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 not root:
return False
if not root.left and not root.right and root.val == targetSum:
return True

return self.hasPathSum(root.left, targetSum - root.val) or \

Choose a reason for hiding this comment

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

binary operatorの前で改行を入れると良さそうです。
https://peps.python.org/pep-0008/#should-a-line-break-before-or-after-a-binary-operator

Copy link
Owner Author

Choose a reason for hiding this comment

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

またやってしまいました(2回目)
これは癖になっているので意識しないとです。ありがとうございました。

self.hasPathSum(root.right, targetSum - root.val)
```
思考ログ:
- 葉を目指しながら```targetSum```を調整していけば良い
- 終端条件で手間取って時間を使う
- 最初ノードがNoneになるまでまで見て、そこでtargetSumが0なら目的のパスを発見、のように実装していた
- 目的のパスがあるかどうかは、葉で判定するように分離することにした
- 葉かどうかの判定をする関数を作っても良かったかも
- ```The number of nodes in the tree is in the range [0, 5000].```なのでデフォルトでは厳しい条件

ループ
```python
# 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 not root:
return False
Comment on lines +48 to +49

Choose a reason for hiding this comment

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

ここも不要ですかね


node_target_sum_pairs = [(root, targetSum)]
while node_target_sum_pairs:
node, target_sum = node_target_sum_pairs.pop()
if not node:
continue
Comment on lines +54 to +55
Copy link

Choose a reason for hiding this comment

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

ただの選択肢の話ですが、node.left or right がNoneでないときだけstackに積むようにすればここ不要になりますね。

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 not node.left and not node.right and node.val == target_sum:
return True

node_target_sum_pairs.append((node.left, target_sum - node.val))
node_target_sum_pairs.append((node.right, target_sum - node.val))

return False
```
思考ログ:
- ループ版、そんなに複雑でないので今回は再帰でなくてこっちでもいいかも

# Step2

講師役目線でのセルフツッコミポイント:
- 再帰実装は以下のようにした方が良さそう
```python
if not root.left and not root.right:
return root.val == targetSum
```

参考にした過去ログなど:
- https://github.com/kazukiii/leetcode/pull/26
> 再帰で書く時、`return find_target_path_sum(node.left, path_sum) or find_target_path_sum(node.right, path_sum)` と書いても、left childからtrueが返ってくれば、短絡評価によってright childは呼び出されないのか -> 自分のコードは冗長な書き方をしていたので修正する必要
- 実装上はこう書いているが、このメリットを意識していなかった
- https://github.com/Yoshiki-Iwasa/Arai60/pull/29
- https://github.com/SuperHotDogCat/coding-interview/pull/37
- orと|
- https://github.com/SuperHotDogCat/coding-interview/pull/37/files#r1661771760
> 論理演算には ブーリアン演算子 and, or および not を使ってください。2つの真偽値に対してビット演算子 &, |, ^ を適用した場合は、それぞれ論理演算 "and", "or", "xor" に相当するブール値を返します。しかしながら、論理演算子 and, or および != を使うほうが &, | や ^ を使うよりも好ましいです。
- https://github.com/fhiyo/leetcode/pull/27
- https://github.com/sakupan102/arai60-practice/pull/26
- https://github.com/Mike0121/LeetCode/pull/5
- https://github.com/rossy0213/leetcode/pull/14
- https://github.com/shining-ai/leetcode/pull/25
- https://github.com/hayashi-ay/leetcode/pull/30
- https://github.com/YukiMichishita/LeetCode/tree/main/112_path_sum

足していく方式
```python
# 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:
def has_path_sum_helper(root: Optional[TreeNode], total: int) -> bool:
if not root:
return False

total += root.val
if not root.left and not root.right:
return total == targetSum

return has_path_sum_helper(root.left, total) or has_path_sum_helper(root.right, total)

return has_path_sum_helper(root, 0)
```
思考ログ:
- 再帰でやるなら引いていったほうが素直な感じ

# Step3

かかった時間:3min

```python
# 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:
def is_leaf(node: Optional[TreeNode]) -> bool:
assert node
return not node.left and not node.right
Comment on lines +131 to +133

Choose a reason for hiding this comment

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

node が None であるケースは例外(Exception)ではないと思うので、シンプルに False を返すのが適切かなと思いました。引数の型は Optional[TreeNode] で None を許容しているにも関わらず、それを入れると例外が返ってくるというのはびっくりするかと。葉であるかの判定を関数化するなら以下のようなコードがよさそうです。

Suggested change
def is_leaf(node: Optional[TreeNode]) -> bool:
assert node
return not node.left and not node.right
def is_leaf(node: Optional[TreeNode]) -> bool:
return node and not node.left and not node.right

また上記を採用する場合、L.141-142 の判定も不要になりますね。(合わせて remain -= node.val でなく L.145 内で remain - node.val == 0 のように扱う必要もありますが)

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 not root:
return False
Comment on lines +135 to +136

Choose a reason for hiding this comment

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

root がないケースをコーナーケースとして取り出さなくても、ループの1回目の処理で L.141-142 で正しく判定できそうです。

Copy link
Owner Author

Choose a reason for hiding this comment

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

ありがとうございます。
脳内トレース漏れしてました。


node_remain_pairs = [(root, targetSum)]
while node_remain_pairs:
node, remain = node_remain_pairs.pop()
if not node:
continue

remain -= node.val
if is_leaf(node) and remain == 0:
return True
Comment on lines +145 to +146
Copy link

Choose a reason for hiding this comment

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

if is_leaf(node):
    return remain == 0

とすると、nodeが葉の場合に無駄にleft, rightをスタックに入れずに済む気がしました。

Copy link
Owner Author

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.

ん、ループでこれやるとまずくないですかね。
最初の葉が見つかった時に残数が0で無ければ探索打ち切っちゃいますよね?

Copy link

Choose a reason for hiding this comment

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

あっ本当ですね...失礼しました 🙏


node_remain_pairs.append((node.left, remain))
node_remain_pairs.append((node.right, remain))

return False
```
思考ログ:
- やっている事に対して、```target_sum```だと違和感があるので```remain```に変更
- ```is_leaf```関数を導入

# Step4

```python
# 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:
def is_leaf(node: TreeNode) -> bool:

Choose a reason for hiding this comment

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

細かいですが、個人的にはclassのprivateメソッドとして定義したいです。汎用的なためhasPathSumが呼び出されるたびにis_leafの関数オブジェクトを作成する必要はないように感じました。

Copy link
Owner Author

Choose a reason for hiding this comment

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

なるほど、確かにそうですね。
こういうご指摘を頂けるのもこの勉強会ならではですね。

return not node.left and not node.right

node_remain_pairs = [(root, targetSum)]
while node_remain_pairs:
node, remain = node_remain_pairs.pop()
if not node:
continue

remain -= node.val

Choose a reason for hiding this comment

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

細かいですが、remainって自動詞なのでremained,leftとかrestとかが適切なのかなと

Copy link
Owner Author

Choose a reason for hiding this comment

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

軽く調べたつもりだったんですけど、remainsにしないといけないんですね。
しかも不可算名詞だから相応しくないですね。。

if is_leaf(node) and remain == 0:
return True

node_remain_pairs.append((node.left, remain))
node_remain_pairs.append((node.right, remain))

return False
```
思考ログ:
- コメントをもとに試行錯誤したが、この形に落ち着いた
- 入り口の```root```チェックを除外
- ```is_leaf```のnodeの型のOptionalを除外

# Step5

```python
```
思考ログ: