-
Notifications
You must be signed in to change notification settings - Fork 0
142 linked list cycle ii medium #20
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
a6318fe
788c257
f22c7fe
9bc21c1
16c8358
3abdd1f
f95dbda
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,142 @@ | ||
| # 問題へのリンク | ||
| [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 | ||
|
|
||
| `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を追加してみた。 | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 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 | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 動詞は三人称単数形にするのが一般的のようなので、 |
||
| 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 | ||
|
Comment on lines
+80
to
+81
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
|
||
| while pointer1 != pointer2: | ||
| pointer1 = pointer1.next | ||
| pointer2 = pointer2.next | ||
| return pointer1 | ||
| ``` | ||
|
|
||
|
|
||
| ## 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: ...` とかけた方が嬉しくなりそう | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. これは確かにそうですね。
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 良かったです。こちらこそレビューありがとうございました。 |
||
| - 予期せぬfalsyなオブジェクトが入ってきた時に、エラーにならずコードが動いてしまう可能性がある | ||
| - 動詞は三人称単数形にするのが一般的なので、`have_cycle`よりも`has_cycle`の方が良い | ||
|
|
||
|
|
||
| # 別解・模範解答 | ||
| フロイドのアルゴリズムを使用する方法。 | ||
|
|
||
| ```python | ||
| class Solution: | ||
| def detectCycle(self, head: ListNode | None) -> ListNode | None: | ||
| if not head: | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. これでも問題無いと思うのですが、個人的には There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. falsy判定なので、明示あったほうが良いと思いました。
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. レビューありがとうございます。
(もしbuilt-inのオブジェクトなら、タプルでもリストでもsetでも良いように |
||
| 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: | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ここでは値の等価性よりオブジェクトの同一性を見たいと思うのでisを使うほうが自然な気がしました。 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 自作オブジェクトの記述を見に行くのがめんどうなので、個人的にはisの方がありがたいです
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. レビューありがとうございます。 |
||
| 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`フラグを使用している。 | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. for-elseはバッドプラクティスなのですね。確かに他の言語には無く、またあまり見かけないので実際目にすると「elseに行くのはどういう条件だっけ?」と悩むことになるので使わないほうがよいというのは納得です。 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. たた、フラグは goto よりも構造化されていないと思いますね。 |
||
|
|
||
| - 時間計算量:`O(n)` | ||
| - 空間計算量:`O(1)` | ||
|
|
||
| # 次に解く問題の予告 | ||
| - [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/) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 = w | ||
| # self.next = None | ||
|
|
||
|
|
||
| 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 | ||
|
|
||
|
|
||
| # @lc code=end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| # | ||
| # @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: | ||
| """ | ||
| 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 | ||
|
|
||
|
|
||
| # @lc code=end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
分かりやすいです。