From a6318fec300b2d43d9e6c4d27b2c56dcf0d16085 Mon Sep 17 00:00:00 2001 From: Kaichi-Irie Date: Sun, 31 Aug 2025 17:30:18 +0900 Subject: [PATCH 1/7] Solve 142_linked_list_cycle_ii_medium --- .../Floyds_algorithm.py | 40 +++++++ 142_linked_list_cycle_ii_medium/README.md | 100 ++++++++++++++++++ 142_linked_list_cycle_ii_medium/step1.py | 27 +++++ 3 files changed, 167 insertions(+) create mode 100644 142_linked_list_cycle_ii_medium/Floyds_algorithm.py create mode 100644 142_linked_list_cycle_ii_medium/README.md create mode 100644 142_linked_list_cycle_ii_medium/step1.py diff --git a/142_linked_list_cycle_ii_medium/Floyds_algorithm.py b/142_linked_list_cycle_ii_medium/Floyds_algorithm.py new file mode 100644 index 0000000..3147190 --- /dev/null +++ b/142_linked_list_cycle_ii_medium/Floyds_algorithm.py @@ -0,0 +1,40 @@ +# +# @lc app=leetcode id=142 lang=python3 +# +# [142] Linked List Cycle II +# + +# @lc code=start +# Definition for singly-linked list. +# class ListNode: +# def __init__(self, x): +# self.val = x +# self.next = None + + +class Solution: + def detectCycle(self, head: ListNode | None) -> ListNode | None: + if not head: + return None + + slow_pointer = head + fast_pointer = head + exists_cycle = False + while fast_pointer and fast_pointer.next: + slow_pointer = slow_pointer.next + fast_pointer = fast_pointer.next.next + if slow_pointer == fast_pointer: + exists_cycle = True + break + + if not exists_cycle: + return None + + slow_pointer = head + while slow_pointer != fast_pointer: + slow_pointer = slow_pointer.next + fast_pointer = fast_pointer.next + return slow_pointer + + +# @lc code=end diff --git a/142_linked_list_cycle_ii_medium/README.md b/142_linked_list_cycle_ii_medium/README.md new file mode 100644 index 0000000..d826684 --- /dev/null +++ b/142_linked_list_cycle_ii_medium/README.md @@ -0,0 +1,100 @@ +# 問題へのリンク +[142. Linked List Cycle II](https://leetcode.com/problems/linked-list-cycle-ii/) + +# 言語 +Python + +# 問題の概要 +与えられた連結リストがサイクルを持つ場合、そのサイクルの開始ノードを返す。サイクルがない場合は`None`を返す。 + +# 自分の解法 + +## step1 + +`visited`セットを用いて到達済みノードを記録する方法。 + + +```python +class Solution: + def detectCycle(self, head: ListNode | None) -> ListNode | None: + visited = set() + node = head + while node: + if node in visited: + return node + visited.add(node) + node = node.next + return node +``` + +- 自作オブジェクトはhashableなので、setやdictのキーとして使用できる + - デフォルトでは`__hash__`メソッドが`hash(id(self))`を返すため、オブジェクトのIDに基づいてハッシュ値が生成される + - デフォルトでは`__eq__`メソッドは`id(self) == id(other)`を返すため、オブジェクトのIDに基づいて等価性が判断される + - `__eq__`メソッドが定義されている場合、`__hash__`メソッドも**適切に**定義する必要がある + - `a==b`が`True`の場合、`hash(a)`と`hash(b)`も等しくなる必要がある + + +- 時間計算量:`O(n)` +- 空間計算量:`O(n)` + +## step2 + + + +## step3 + +## step4 (FB) + + + +# 別解・模範解答 +フロイドのアルゴリズムを使用する方法。 + +```python +class Solution: + def detectCycle(self, head: ListNode | None) -> ListNode | None: + if not head: + return None + + slow_pointer = head + fast_pointer = head + exists_cycle = False + while fast_pointer and fast_pointer.next: + slow_pointer = slow_pointer.next + fast_pointer = fast_pointer.next.next + if slow_pointer == fast_pointer: + exists_cycle = True + break + + if not exists_cycle: + return None + + slow_pointer = head + while slow_pointer != fast_pointer: + slow_pointer = slow_pointer.next + fast_pointer = fast_pointer.next + return slow_pointer +``` + +まずは、2つのポインタを用いてサイクルの存在を検出する。次に、サイクルの開始ノードを特定する。 +1. 2つのポインタ(`slow`と`fast`)を用意し、`slow`は1ステップずつ、`fast`は2ステップずつ進める。 +2. もし`slow`と`fast`が衝突した場合、サイクルが存在する。 +3. サイクルの開始ノードを特定するために、`slow`をリストの先頭に戻し、`fast`はそのままの位置に置く。両方を1ステップずつ進めて次に衝突するノードがサイクルの開始ノードである。 + +- ループが`break`されず。正常に終了したかどうかを表す`for ... else`はEffective Pythonではバッドプラクティスとされているため、`exists_cycle`フラグを使用している。 + + + +- 時間計算量:`O(n)` +- 空間計算量:`O(1)` + +# 想定されるフォローアップ質問 + +## CS 基礎 + +## システム設計 + +## その他 + +# 次に解く問題の予告 +- Permutations diff --git a/142_linked_list_cycle_ii_medium/step1.py b/142_linked_list_cycle_ii_medium/step1.py new file mode 100644 index 0000000..2f5c654 --- /dev/null +++ b/142_linked_list_cycle_ii_medium/step1.py @@ -0,0 +1,27 @@ +# +# @lc app=leetcode id=142 lang=python3 +# +# [142] Linked List Cycle II +# + +# @lc code=start +# Definition for singly-linked list. +# class ListNode: +# def __init__(self, x): +# self.val = x +# self.next = None + + +class Solution: + def detectCycle(self, head: ListNode | None) -> ListNode | None: + visited = set() + node = head + while node: + if node in visited: + return node + visited.add(node) + node = node.next + return node + + +# @lc code=end From 788c2572db3d6fc3275578997b26814e72032d1c Mon Sep 17 00:00:00 2001 From: Kaichi-Irie Date: Wed, 10 Sep 2025 09:55:03 +0900 Subject: [PATCH 2/7] Add step3 --- 142_linked_list_cycle_ii_medium/step3.py | 31 ++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 142_linked_list_cycle_ii_medium/step3.py diff --git a/142_linked_list_cycle_ii_medium/step3.py b/142_linked_list_cycle_ii_medium/step3.py new file mode 100644 index 0000000..e8e1771 --- /dev/null +++ b/142_linked_list_cycle_ii_medium/step3.py @@ -0,0 +1,31 @@ +# +# @lc app=leetcode id=142 lang=python3 +# +# [142] Linked List Cycle II +# + +# @lc code=start +# Definition for singly-linked list. +# class ListNode: +# def __init__(self, x): +# self.val = x +# self.next = None + + +class Solution: + NOT_FOUND = -1 + + def detectCycle(self, head: ListNode | None) -> ListNode | None: + if not head: + return None + seen_nodes = set() + node = head + while node: + if node in seen_nodes: + return node + seen_nodes.add(node) + node = node.next + return None + + +# @lc code=end From f22c7fe0eca63c5ed764b40616922d45248e8e6e Mon Sep 17 00:00:00 2001 From: Kaichi-Irie Date: Wed, 10 Sep 2025 20:20:54 +0900 Subject: [PATCH 3/7] Refactor cycle detection implementation and add two-pointer solution --- 142_linked_list_cycle_ii_medium/step3.py | 4 +- .../step3_two_pointers.py | 40 +++++++++++++++++++ 2 files changed, 41 insertions(+), 3 deletions(-) create mode 100644 142_linked_list_cycle_ii_medium/step3_two_pointers.py diff --git a/142_linked_list_cycle_ii_medium/step3.py b/142_linked_list_cycle_ii_medium/step3.py index e8e1771..62dd3e9 100644 --- a/142_linked_list_cycle_ii_medium/step3.py +++ b/142_linked_list_cycle_ii_medium/step3.py @@ -8,13 +8,11 @@ # Definition for singly-linked list. # class ListNode: # def __init__(self, x): -# self.val = x +# self.val = w # self.next = None class Solution: - NOT_FOUND = -1 - def detectCycle(self, head: ListNode | None) -> ListNode | None: if not head: return None diff --git a/142_linked_list_cycle_ii_medium/step3_two_pointers.py b/142_linked_list_cycle_ii_medium/step3_two_pointers.py new file mode 100644 index 0000000..8f0660f --- /dev/null +++ b/142_linked_list_cycle_ii_medium/step3_two_pointers.py @@ -0,0 +1,40 @@ +# +# @lc app=leetcode id=142 lang=python3 +# +# [142] Linked List Cycle II +# + +# @lc code=start +# Definition for singly-linked list. +class ListNode: + def __init__(self, x): + self.val = x + self.next = None + + +class Solution: + def detectCycle(self, head: ListNode | None) -> ListNode | None: + if not head: + return None + slow_pointer = head + fast_pointer = head + have_cycle = False + while fast_pointer.next and fast_pointer.next.next: + fast_pointer = fast_pointer.next.next + slow_pointer = slow_pointer.next + if fast_pointer == slow_pointer: + have_cycle = True + break + if not have_cycle: + return None + + pointer1 = head + pointer2 = fast_pointer + + while pointer1 != pointer2: + pointer1 = pointer1.next + pointer2 = pointer2.next + return pointer1 + + +# @lc code=end From 9bc21c1162aa0a0e6972ea2efb8a41b06971464b Mon Sep 17 00:00:00 2001 From: Kaichi-Irie Date: Wed, 10 Sep 2025 20:29:53 +0900 Subject: [PATCH 4/7] Add docstring to detectCycle method to clarify functionality --- 142_linked_list_cycle_ii_medium/step3_two_pointers.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/142_linked_list_cycle_ii_medium/step3_two_pointers.py b/142_linked_list_cycle_ii_medium/step3_two_pointers.py index 8f0660f..8b8104b 100644 --- a/142_linked_list_cycle_ii_medium/step3_two_pointers.py +++ b/142_linked_list_cycle_ii_medium/step3_two_pointers.py @@ -14,6 +14,10 @@ def __init__(self, x): class Solution: def detectCycle(self, head: ListNode | None) -> ListNode | None: + """ + Detect if a cycle exists and if so, where the cycle starts. + This uses Floyd's algorithm for efficient space complexity. + """ if not head: return None slow_pointer = head From 16c8358355d731d1cc54ef1dd9daa2c65476a434 Mon Sep 17 00:00:00 2001 From: Kaichi-Irie Date: Wed, 10 Sep 2025 20:35:13 +0900 Subject: [PATCH 5/7] Remove unnecessary None checks --- 142_linked_list_cycle_ii_medium/step3.py | 2 -- .../step3_two_pointers.py | 12 +++++------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/142_linked_list_cycle_ii_medium/step3.py b/142_linked_list_cycle_ii_medium/step3.py index 62dd3e9..87e722c 100644 --- a/142_linked_list_cycle_ii_medium/step3.py +++ b/142_linked_list_cycle_ii_medium/step3.py @@ -14,8 +14,6 @@ class Solution: def detectCycle(self, head: ListNode | None) -> ListNode | None: - if not head: - return None seen_nodes = set() node = head while node: diff --git a/142_linked_list_cycle_ii_medium/step3_two_pointers.py b/142_linked_list_cycle_ii_medium/step3_two_pointers.py index 8b8104b..5f45b7a 100644 --- a/142_linked_list_cycle_ii_medium/step3_two_pointers.py +++ b/142_linked_list_cycle_ii_medium/step3_two_pointers.py @@ -6,10 +6,10 @@ # @lc code=start # Definition for singly-linked list. -class ListNode: - def __init__(self, x): - self.val = x - self.next = None +# class ListNode: +# def __init__(self, x): +# self.val = x +# self.next = None class Solution: @@ -18,12 +18,10 @@ def detectCycle(self, head: ListNode | None) -> ListNode | None: Detect if a cycle exists and if so, where the cycle starts. This uses Floyd's algorithm for efficient space complexity. """ - if not head: - return None slow_pointer = head fast_pointer = head have_cycle = False - while fast_pointer.next and fast_pointer.next.next: + while fast_pointer and fast_pointer.next: fast_pointer = fast_pointer.next.next slow_pointer = slow_pointer.next if fast_pointer == slow_pointer: From 3abdd1fc20e81b9748d822013aa30e74615fa79a Mon Sep 17 00:00:00 2001 From: Kaichi-Irie Date: Wed, 10 Sep 2025 20:38:04 +0900 Subject: [PATCH 6/7] Add step3 implementation and enhance docstring for cycle detection using Floyd's algorithm --- 142_linked_list_cycle_ii_medium/README.md | 60 ++++++++++++++++++----- 1 file changed, 48 insertions(+), 12 deletions(-) diff --git a/142_linked_list_cycle_ii_medium/README.md b/142_linked_list_cycle_ii_medium/README.md index d826684..758b0ef 100644 --- a/142_linked_list_cycle_ii_medium/README.md +++ b/142_linked_list_cycle_ii_medium/README.md @@ -39,9 +39,53 @@ class Solution: ## step2 +## step3 +`step3.py` +```python +class Solution: + def detectCycle(self, head: ListNode | None) -> ListNode | None: + seen_nodes = set() + node = head + while node: + if node in seen_nodes: + return node + seen_nodes.add(node) + node = node.next + return None +``` + +`step3_two_pointers.py` +やはりフロイドのアルゴリズムは読んでいてわかりにくいので、docstringを追加してみた。 + +```python +class Solution: + def detectCycle(self, head: ListNode | None) -> ListNode | None: + """ + Detect if a cycle exists and if so, where the cycle starts. + This uses Floyd's algorithm for efficient space complexity. + """ + slow_pointer = head + fast_pointer = head + have_cycle = False + while fast_pointer and fast_pointer.next: + fast_pointer = fast_pointer.next.next + slow_pointer = slow_pointer.next + if fast_pointer == slow_pointer: + have_cycle = True + break + if not have_cycle: + return None + + pointer1 = head + pointer2 = fast_pointer + + while pointer1 != pointer2: + pointer1 = pointer1.next + pointer2 = pointer2.next + return pointer1 +``` -## step3 ## step4 (FB) @@ -83,18 +127,10 @@ class Solution: - ループが`break`されず。正常に終了したかどうかを表す`for ... else`はEffective Pythonではバッドプラクティスとされているため、`exists_cycle`フラグを使用している。 - - - 時間計算量:`O(n)` - 空間計算量:`O(1)` -# 想定されるフォローアップ質問 - -## CS 基礎 - -## システム設計 - -## その他 - # 次に解く問題の予告 -- Permutations +- [Capacity To Ship Packages Within D Days](https://leetcode.com/problems/capacity-to-ship-packages-within-d-days/) +- [Longest Increasing Subsequence](https://leetcode.com/problems/longest-increasing-subsequence/) +- [Unique Paths II](https://leetcode.com/problems/unique-paths-ii/) From f95dbda64f734aa20be54d190cf1b9f7eafaae92 Mon Sep 17 00:00:00 2001 From: Kaichi-Irie Date: Fri, 12 Sep 2025 21:13:32 +0900 Subject: [PATCH 7/7] Enhance README.md with best practices for object comparison and None checks --- 142_linked_list_cycle_ii_medium/README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/142_linked_list_cycle_ii_medium/README.md b/142_linked_list_cycle_ii_medium/README.md index 758b0ef..d3ae105 100644 --- a/142_linked_list_cycle_ii_medium/README.md +++ b/142_linked_list_cycle_ii_medium/README.md @@ -88,7 +88,13 @@ class Solution: ## step4 (FB) - +- 自作オブジェクトの比較は`is`を使うべき + - もし`__eq__`がオーバーライドされている場合、`==`は意図しない動作をする可能性がある +- `None`チェックは`if not head:`よりも、`if head is None:`の方が今は明示的で良い + - ここでもメソッドのオーバーライドで予期せずfalsy判定になると良くない。 + - 一方で、空のListNodeはfalsyとなるように実装されている場合、`if head is None: ... elif head is EmptyNode: ...` と条件分岐が二つになってしまうので、`if not head: ...` とかけた方が嬉しくなりそう + - 予期せぬfalsyなオブジェクトが入ってきた時に、エラーにならずコードが動いてしまう可能性がある +- 動詞は三人称単数形にするのが一般的なので、`have_cycle`よりも`has_cycle`の方が良い # 別解・模範解答