Skip to content
Open
Show file tree
Hide file tree
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
24 changes: 24 additions & 0 deletions 142. Linked List Cycle II/note.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# step1

set を使用した space complexity O(N)の方法はすぐに思いつく.
問題は O(1)の場合. Floyd のアルゴリズムで, 合流までの回数などを用いて計算すれば求まりそうだが, あまり自明ではないだろう. Brent のアルゴリズムを使えば求められるだろうが, これも常識の範囲には入らず, 読むには負荷がかかるのではないだろうか.
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Brent’s Cycle Detection Algorithmは自分は知らなかったです。

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

class Solution:
    def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]:
        def find_cycle_length(start_node):
            power = 1
            length = 0
            slow = start_node
            fast = start_node
            while fast:
                fast = fast.next
                length += 1
                if slow == fast:
                    return length
                if length == power:
                    power *= 2
                    length = 0
                    slow = fast
            return -1
        
        def step_node_by_n(node, n):
            while n > 0:
                node = node.next
                n -= 1
            return node
        
        cycle_length = find_cycle_length(head)
        if cycle_length == -1:
            return None
        # 開始点と開始点からcycle_lengthだけ動いた位置から1つずつポインターを進めて合流地点がループの開始地点になる
        p1 = head
        p2 = step_node_by_n(head, cycle_length)
        while p1 != p2:
            p1 = p1.next
            p2 = p2.next
        return p1

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.

ありがとうございます!141. Linked List Cycleを解いていたときにこのwikiを見てたまたま知っていたのですが, 勝手にループの開始地点が分かると勘違いしていました. 実装参考になります, Floydのときと同じような形で開始点も見つけられそうなのですね.

ということで今回は set を使用したアルゴリズムで書く.

やはり Floyd の方法でも解く.
アルゴリズムは editorial にある通り. ループは 2\*a+b 回ほどまわる.
メソッドの命名が統一されていないが, leetcode の都合上 detectCycle は変更できないのでしかたがない.

# step2

見た回答

- Google docs の sample answer
- https://discord.com/channels/1084280443945353267/1195700948786491403/1196010117120925777
- キャンベルの法則を知る
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.

なるほど, ”名前を知ると意識できるようになる”という法則にも名前がついているのですね.
ジョシュアツリーの法則で検索したら他にもいろいろな法則を知ることができました. (https://ktr-05.hatenablog.com/entry/2019/07/07/184436)

- https://discord.com/channels/1084280443945353267/1200089668901937312/1205725280556023898
- "エッジケースを先にケアしておくことで読む人がエッジケースの考慮ができてるかを確認する手間が減るメリットもある"というのは参考になる.
- メソッドの中で関数を定義するのと, クラスに内部用のメソッドを追加するのだとどういうメリット・デメリットがあるだろうか. 他にも catchup point を知りたいメソッドがある場合は, メソッドを追加する方法が良いだろう. 一方で"\_"から始まるメソッドは完璧には private ではないというデメリットもある.
- from_start, from_catchup という命名は良いと思った. また, 自分のコードの collision という命名は少々わかりにくい.

以上を参考に書く.
set を用いた方法はそんなに変わらず.
27 changes: 27 additions & 0 deletions 142. Linked List Cycle II/step1_floyd.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None

class Solution:
def _get_collision_node(self, head: Optional[ListNode]) -> Optional[ListNode]:
fast_cursor = head
slow_cursor = head
while fast_cursor and fast_cursor.next:
fast_cursor = fast_cursor.next.next
slow_cursor = slow_cursor.next
if fast_cursor == slow_cursor:
return fast_cursor
return None

def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]:
collision_node = self._get_collision_node(head)
if not collision_node:
return None
head_cursor = head
collision_cursor = collision_node
while head_cursor != collision_cursor:
head_cursor = head_cursor.next
collision_cursor = collision_cursor.next
return head_cursor
15 changes: 15 additions & 0 deletions 142. Linked List Cycle II/step1_set.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None

class Solution:
def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]:
seen_nodes = set()
while head:
if head in seen_nodes:
return head
seen_nodes.add(head)
head = head.next
return None
27 changes: 27 additions & 0 deletions 142. Linked List Cycle II/step2_floyd.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None

class Solution:
def _get_catchup_node(self, head: Optional[ListNode]) -> Optional[ListNode]:
fast_cursor = head
slow_cursor = head
while fast_cursor and fast_cursor.next:
fast_cursor = fast_cursor.next.next
slow_cursor = slow_cursor.next
if fast_cursor == slow_cursor:
return fast_cursor
return None

def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]:
catchup_node = self._get_catchup_node(head)
if not catchup_node:
return None
from_head = head
from_catchup = catchup_node
while from_head != from_catchup:
from_head = from_head.next
from_catchup = from_catchup.next
return from_head
15 changes: 15 additions & 0 deletions 142. Linked List Cycle II/step2_set.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None

class Solution:
def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]:
seen_nodes = set()
while head:
if head in seen_nodes:
return head
seen_nodes.add(head)
head = head.next
return None
27 changes: 27 additions & 0 deletions 142. Linked List Cycle II/step3_floyd.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None

class Solution:
def _search_catchup_node(self, head: Optional[ListNode]) -> Optional[ListNode]:
fast_cursor = head
slow_cursor = head
while fast_cursor and fast_cursor.next:
fast_cursor = fast_cursor.next.next
slow_cursor = slow_cursor.next
if fast_cursor == slow_cursor:
return fast_cursor
return None

def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]:
catchup_node = self._search_catchup_node(head)
if not catchup_node:
return None
from_start = head
from_catchup = catchup_node
while from_start != from_catchup:
from_start = from_start.next
from_catchup = from_catchup.next
return from_start
16 changes: 16 additions & 0 deletions 142. Linked List Cycle II/step3_set.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None

class Solution:
def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]:
seen_nodes = set()
cursor = head
while cursor:
if cursor in seen_nodes:
return cursor
seen_nodes.add(cursor)
cursor = cursor.next
return None