Skip to content
Merged
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
102 changes: 102 additions & 0 deletions 競技プロ就活部PR用/141. Linked List Cycle pr.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
フロイドの循環法の説明:
https://discord.com/channels/1084280443945353267/1246383603122966570/1252209488815984710
```
はい。2歩ずつ走るうさぎと1歩ずつ歩くかめが、ある地点でぶつかったとします。そこを衝突点と呼びましょう。衝突点が見つかったあとに、衝突点とスタート地点から1歩ずつうさぎとかめを歩かせて、衝突するところが、合流地点である、ということを理解したいということですね。

うさぎとかめは、衝突点で出会った後に、うさぎとかめは、いま来た道を戻るように言われました。うさぎもかめも同じ速さで1歩ずつ歩いて戻ります。このとき、うさぎは一周してから戻りますが、かめはそのまま戻ります。

かめがスタート地点に戻った時、うさぎはどこにいるでしょうか。実は、うさぎは衝突点にいます。なぜかというと、うさぎは倍速で走っているからです。スタート地点から衝突点を通って衝突点に到達するうさぎルートの長さは、スタートから衝突点に到達するかめルートの2倍だからです。

ところで、この戻っていく時、うさぎとかめは、衝突点から同じ速さで歩いて戻っているので、合流点までは一緒にいましたね。

さて、ここまでの話を動画にして逆回しにしてみましょう。

うさぎとかめは、それぞれ衝突点とスタート地点から同じ速さで後ろ向きに歩き始めます。そして、合流地点から一緒に後ろ向きに歩き始め、そして衝突点に到達します。
```

* ObjectがHashableかどうかは常に考える。
* ListNodeはHashable、Hashableでないものは、List, Dictなどのmutalbleなオブジェクト。
* https://docs.python.org/ja/3/glossary.html#term-hashable
* ユーザ定義クラスはデフォルト場合hashableであり、ハッシュ値はid()に由来する。
* ハッシュ可能なオブジェクトの比較には == 演算子で値を比較、オブジェクトの同一性チェックには is 演算子を使用
* 選択肢として思いついたのは、setに使いしていく方法と、Floyd。
* 再帰は選択肢になかったので頭に入れておく。

## setによる解法

### 1回目 (2m32s)
時間計算量: O(N)<br>
空間計算量: O(N)<br>

* 素直な問題なため真っ直ぐ解けた。
* headがNoneだった場合を考慮して、if not headを書いても良いかもしれない。
* ユーザーの目的によっては、assertしてエラーにしても良いかも。

```python
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None

class Solution:
def hasCycle(self, head: Optional[ListNode]) -> bool:
seen = set()
while head:
if head in seen:
return True
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

私の間違いだったら申し訳ないのですが,この下にseenにheadが含まれない場合はaddする必要がある気がします.

else:
    seen.add(head)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

私もadd必要だと思います。
ifの中でreturnしているので、ifの外であればelseはなくてもよさそうです。

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.

すみません、ご指摘頂きありがとうございます。
コピペの段階で誤って消したみたいです。クリティカルなのでここだけ部分的に修正しました。

seen.add(head)
head = head.next
return False
```


## フロイドの循環検出法(Floyd's cycle-finding algorithm)による解法
時間計算量: O(N)<br>
空間計算量: O(1)<br>

計算量に関するmemo from goto-untrappedさん:
* 出会わなかったらO(n/2)=O(n)、
* 出会う場合、サイクルに入るまで最大は、slowのO(n)、
* 入ってから出会うまでが最大は、1ずつ縮まるのでO(n)、
* なのでO(n)+O(n)=O(n)
https://github.com/goto-untrapped/Arai60/pull/21/commits/e0545f02716ea1c5824cc560a5602f8025532cf4


```python
class Solution:
def hasCycle(self, head: Optional[ListNode]) -> bool:
slow = head
fast = head
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 = slow = head

も選択肢でしょうか?

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.

ありがとうございます。
そうですね、個人的に少し1行に詰め込むと読みづらいと感じる時があるので、この書き方にしています。


while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow is fast:
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

if slow == fast:ではなくif slow is fast:とするのには何か理由があるのでしょうか。

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.

ListNode同士のオブジェクトの比較なので、isにしています。ここは同一のListNodeの比較想定なので、=でも問題ないですね。

return True
return False
```

## 再帰による解法
Copy link
Copy Markdown

@NobukiFukui NobukiFukui Jul 6, 2024

Choose a reason for hiding this comment

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

様々な解法をご検討されており,よいと思います.
ほかにも,
・関数を用いる方法(以下の部分で実装されていますが,フロイドの循環検出法でも可能かと思いました)
・無限ループを用いる方法

class Solution:
    def hasCycle(self, head: Optional[ListNode]) -> bool:
        fast = slow = head
        while 1:
            if fast is None or fast.next is None:
                return None
            slow = slow.next
            fast = fast.next.next
            if slow == fast:
                return True

もいかがでしょうか

以下の議論も参考になります
https://discordapp.com/channels/1084280443945353267/1192728121644945439/1194203372115464272
https://discordapp.com/channels/1084280443945353267/1221030192609493053/1225674901445283860

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.

ありがとうございます。無限ループの書き方もわかりやすいですね。参考になります。
参考議論もありがとうございます!

時間計算量: O(N)<br>
空間計算量: O(N)<br>

* もう少し良い関数名があるかも。
* 復習として、クラスメソッドでない場合はselfは不要。
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

これはおそらくクラスメソッドではなくインスタンスメソッドの話かなと思いました。
(クラスメソッドの最初の引数は一般的にはselfではなくcls)

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.

ありがとうございます。ご指摘の通りインスタンスメソッドですね。



```python
class Solution:
def hasCycle(self, head: Optional[ListNode]) -> bool:

def hasCycleHelper(node: Optional[ListNode], seen: Set[ListNode]) -> bool:
if not node:
return False
if node in seen:
return True
seen.add(node)
return hasCycleHelper(node.next, seen)

seen = set()
return hasCycleHelper(head, seen)
```