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
134 changes: 134 additions & 0 deletions 142. Linked List Cycle II/142. Linked List Cycle II.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
問題文: https://leetcode.com/problems/linked-list-cycle-ii/

# step1: 何も見ないで書く
## setを使った方法
- 時間計算量: O(n), 空間計算量 O(n)。
- 前回もらったコメントを参考に条件を整えた。
- ループ条件は ```while node:```でもいいが、個人的に読みにくい気がしたので```is not```で判定した。
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

とても良いと思います。

今回の問題の入力の制約からしてありえないということは抜きにすると、たとえばnodeが空リストだったりしてもwhile nodeはFalse判定されてしまいます。これが意図せぬ挙動を引き起こすこともあるため、Noneの確認をするときにはis not Noneと書いた方が安全だと思います

```python
class Solution:
def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]:
visited = set()
node = head
while node is not None:
if node in visited:
return node
visited.add(node)
node = node.next
return None

```

## フロイドの方法
- 時間計算量: O(n), 空間計算量 O(1)。
- 自力では思いつかなかったので、新井さんの解説を見た。
- ループがネストされて読みにくいが、読みやすくする方法をすぐには思いつかなかったのでとりあえずこれで提出。
```python
class Solution:
def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]:
if head is None:
return None
fast = head
slow = head
while fast.next is not None and fast.next.next is not None:
fast = fast.next.next
slow = slow.next
if fast is slow:
slow = head
while fast is not slow:
fast = fast.next
slow = slow.next
return slow
return None

```

# step2: 他の人のレビューを見てコードを整える
## setを使った方法
- 他の人は```set```オブジェクトの中身まで気にしていたが、自分は見落としていたのでドキュメントを調べた。
- set型はハッシュ値を持つオブジェクトを格納するためのオブジェクトで、メンバーの判定```x in set```はハッシュ値を使ってオブジェクトの同一性をチェックしている。\
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

めっちゃ細かいのですが、ハッシュでは同一性ではなく等価性をチェックしている、という方が一般的な気がします。
ハッシュ値が定義されているならば==演算子が定義されていることになりますが、==は等価性を確認するための演算子(という認識)であるからです、おそらく...

https://docs.python.org/ja/3/library/stdtypes.html#set-types-set-frozenset
- したがって、```node in visited```でメンバーと判定された```node```を返せば、サイクルの始点と同一のオブジェクトが返されることが保証されている。
```python
class Solution:
def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]:
visited = set()
node = head
while node is not None:
if node in visited:
return node
visited.add(node)
node = node.next
return None

```

## フロイドの方法
- サイクルの検知とサイクルの始点の検出を分けると良いというコメントを参考に整えた。
- 最初はフラグ変数で実装していたが、小田さんのコメントで構造化が不十分と指摘されていた。そのため、典型コメント集の「コードの整え方」を参考に```while else```構文で実装した。
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

次に続くものなどとの関係で他のものを使うこともあるので、解き進めながら時々思い出してください。

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

すでにこちらにコメントをした通りですが、 while else 構文は避けたほうが無難だと思います。
MasukagamiHinata/Arai60#5 (comment)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

フラグよりはましとは思いますが関数化するほうがいいでしょう。

- ループのネストをやめたので始点検出のための2つめのループで新しい変数名を使いたくなる気持ちになりやすくなった気がする。
- step1のコードの読みにくさはループのネストだけでなく変数名の流用も影響していたと気が付いた。
- 2つめのループでウサギ役の変数にどのような名前を付けるか迷ったが、無難にfrom_meetingとした。from_collisionにしようかとも思ったがネットワークの文脈で出てくる専門用語と名前がかぶって紛らわしいのでやめた。
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

from_start, from_meetingという変数名はわかりやすいと思いました!

```python
class Solution:
def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]:
if head is None:
return None
fast = head
slow = head
while fast.next is not None and fast.next.next is not None:
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

fast is None and fast.next is not None
と書いた方が個人的には好きです。移動できる限り移動していく、ということがそのまま条件になっているからです。

fast = fast.next.next
slow = slow.next
if fast is slow:
break
else:
return None
from_start = head
from_meeting = fast
while from_start is not from_meeting:
from_start = from_start.next
from_meeting = from_meeting.next
return from_start

```

# step3: 10分以内にミスなく3回書く
## setを使った方法
```python
class Solution:
def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]:
visited = set()
node = head
while node is not None:
if node in visited:
return node
visited.add(node)
node = node.next
return None

```

## フロイドの方法
```python
class Solution:
def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]:
if head is None:
return None
fast = head
slow = head
while fast.next is not None and fast.next.next is not None:
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

この部分ですが、
while fast is not None and fast.next is not None:
で良いと思うのですが、こうしなかった理由はなにかありますでしょうか?

上記とは別の観点のコメントとなりますが、
whileループ内のネストが浅くなるように工夫されていて良いと思いました。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

whileの条件については特に理由はなく、完全に私が見落としてただけです。
while fast is not None and fast.next is not None:
は簡潔ですし、冒頭のheadがNoneかどうかの判定も必要なくなるのでこっちの方がいいですね。

fast = fast.next.next
slow = slow.next
if fast is slow:
break
else:
return None
from_start = head
from_meeting = fast
while from_start is not from_meeting:
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

この分量だと迷うところですが「循環を検知する」や「循環の始点を検知する」をそれぞれ関数化してみてもいいかもしれません。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

ありがとうございます!step4で実装してみます!

from_start = from_start.next
from_meeting = from_meeting.next
return from_start

```