Skip to content

Add Linked List Cycle II solution and explanation#10

Open
yakataN wants to merge 1 commit into
mainfrom
Linked-List-Cycle-II
Open

Add Linked List Cycle II solution and explanation#10
yakataN wants to merge 1 commit into
mainfrom
Linked-List-Cycle-II

Conversation

@yakataN
Copy link
Copy Markdown
Owner

@yakataN yakataN commented Sep 1, 2025

Document the implementation and thought process for detecting a cycle in a linked list using both set and two-pointer methods.
Copy link
Copy Markdown

@akmhmgc akmhmgc left a comment

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]:
# ListNode -> indexとするdictをつくれればいけるのだが、こういう複雑な構造もkeyになれる、つまりHash化できるのかがわからない(多分できない)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pythonではhash値を計算するメソッドが定義されていればハッシュテーブルのキーとして利用することができるようです。

ユーザーが定義したクラスはhash値の計算が可能なので利用可能です。
https://docs.python.org/3/glossary.html#term-hashable

class MyClass:
    def __init__(self, value):
        self.value = value

a = MyClass(10)
b = MyClass(10)

d = {a: "first", b: "second"}
print(d)
{<__main__.MyClass object at 0x102a15a90>: 'first', <__main__.MyClass object at 0x1029cb250>: 'second'}

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.

知らなかった&調べなかったですね
ありがとうございます

Comment on lines +101 to +102
head = head.next
slow_pointer = slow_pointer.next
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

headが動くのが違和感があるのと、ここのコンテキストだとheadもslow_pointerも1ずつ動いているので別の名前が良いように感じました。

slowとfastで出会った点と、開始点という意味で、from_startfrom_meeting等はいかがでしょうか。

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.

個人的にrenameのために変数増やすのが違和感あるのですが、一つの変数に複数の仕事させるほうが常識ないのだと理解しました。
renameを積極的に取り入れようと思います。
コメントありがとうございます。

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

rename が多くなる場合は関数に切り出すと見通しが良くなるかなと思います。

具体的には、while head is not slow_pointer: の時点ではslow, fastという概念はもうないので、最初からslow, fastを関数に切り出しておくと、各処理で何をやっているかを変数で表現しやすくなるのではと思います。

...
        def find_intersection(node):
            slow = node
            fast = node

            while fast is not None and fast.next is not None:
                slow = slow.next
                fast = fast.next.next

                if slow is fast:
                    return slow
            return None
        intersection = find_intersection(head)
        if intersection is None:
            return None
        finder = head

        while finder is not intersection:
...

```python
class Solution:
def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]:
seen_nodes = 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.

あくまで個人的な感想ですが、seenよりもvisitedの方がノードを処理している様子が想像出来るので好みです。

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.

今までseenで書いたところ全てで指摘されているので、こういう1ループしてメモする変数はvisitedのほうが適しているんだと理解しました。
コメントありがとうございます。

Copy link
Copy Markdown

@nanae772 nanae772 Sep 2, 2025

Choose a reason for hiding this comment

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

こういう1ループしてメモする変数はvisited

おそらくですが連結リストを有向グラフのように見たときに、この処理をグラフ探索のように考えると単に見る(see)よりノードを訪れていく(visit)という単語がしっくり来るのかなというイメージです。
私も最初はcheckedとかにしていて別にseenとかでもいいかなと思ったりしましたが他の方が分かりやすいと思う変数名であることに越したことはないのでvisitedにしています。

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 でよいと思います。
Visitor pattern などというので visited も自然なものでしょう。

変数名については LLM に聞いてみるのも一つです。

Copy link
Copy Markdown

@nanae772 nanae772 left a comment

Choose a reason for hiding this comment

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

お疲れ様です。最近同じ問題を解いたので少しコメントさせていただきました。

```

だいぶすっきりした。
Linked List Cycleでもあった、2 pointerを用いた方法も書こうとしたが、追いついたところが the head of a linked list になるわけではなく厳しい。slow_pointerの動いた数を保存しておいて、1つずつ前にずらせばどこかで答えに当たる。2分探索すればそれほどかからないが、正攻法ではないように思える。
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分探索というのはheadから衝突地点のノードを一直線で見たときに、どこかのノードで「サイクルの中である」というbool値がFalseからTrueに切り替わるのでその境界を二分探索で見つけるということでしょうか?
その場合、「サイクルの中である」ことを判定する処理はどのように行う想定でしたでしょうか?

Copy link
Copy Markdown
Owner Author

@yakataN yakataN Sep 2, 2025

Choose a reason for hiding this comment

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

この2分探索というのはheadから衝突地点のノードを一直線で見たときに、どこかのノードで「サイクルの中である」というbool値がFalseからTrueに切り替わるのでその境界を二分探索で見つけるということでしょうか?

その意味です

その場合、「サイクルの中である」ことを判定する処理はどのように行う想定でしたでしょうか?

衝突時点から1周するまでに見ているノードと一致する瞬間があるかを見る想定でした

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

ご回答ありがとうございます。

まず二分探索について、配列とは異なり連結リストAのi番目の要素A[i]にアクセスするためには毎回先頭からiステップ進めなければいけないので二分探索の各チェックごとにO(N)かかり全体としての計算量は
O(NlogN) * (サイクル内判定にかかる計算量)
となるかと思われます。

一方で単に先頭から1つずつ愚直に確かめていく線形探索は
O(N) * (サイクル内判定にかかる計算量)
となるのでこの場合は二分探索を採用するメリットがあまり無いのではないかと考えました。

衝突時点から1周するまでに見ているノードと一致する瞬間があるかを見る想定でした

こちら理解できました!
私は「サイクルの長さを求めてから見ているノードからスタートしてその長さ分nextを見ていって元に戻ってこれたらTrue」みたいなことをしていたのですが、サイクルの長さを求めるということをしなくてもよい分そちらの方がスマートですね。良いやり方だと思いました。


- 最初pos(index)を返すものと勘違いしてごちゃごちゃしてしまった。> the `head` of a linked list, return _the node where the cycle begins_) と明記してあったが、見逃している。返り値の型も見逃していて2敗。そもそもの英語力が足りていない話もある。
- seen_nodeは単にnodeで良さそう
- `while node is not None:`でループを回したい(無限ループはどこで抜けるか見るのに認知負荷が高いため)。is_cycledが使えなくなるが、ループ抜けた時に`node is 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.

(無限ループはどこで抜けるか見るのに認知負荷が高いため)

安易に無限ループを使ってしまいがちなのですが、言われてみると確かにどこでbreak, returnしているか注意深く見ないといけないので認知負荷がかかりますね…。気をつけようと思いました。

Comment on lines +166 to +167
slow_pointer = head.next
fast_pointer = head.next.next
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

私はこの部分を

        slow_pointer = head
        fast_pointer = head

とどちらもヘッドからスタートしたのですが一個進めたところからでもいいですね。
こう書かれている方がslow,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.

どちらもheadにしてしまうと169行目のwhileの判定に引っかかるというプログラム面での問題もあり、こうしています。

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_pointer is not None and fast_pointer.next is not Noneのほうにしていたので気づきませんでした。補足ありがとうございます!

Copy link
Copy Markdown

@t-ooka t-ooka left a comment

Choose a reason for hiding this comment

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

レビューしてみました!コードは参考程度にお願いします 🙇

```python
class Solution:
def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]:
seen_nodes = 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.

同じ感想を持ちました!

slow_pointer = head.next
fast_pointer = head.next.next

while slow_pointer is not fast_pointer:
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_pointer が NULL にならないこと(循環がないこと)を判定しておいて、
ループの中で、slow_pointer == fast_pointer を判定するやり方もあるかと思いました。

while fast_pointer and fast_pointer.next:
   slow_pointer == slow_pointer.next
   fast_pointer == fast_pointer.next.next
   if slow_pointer == fast_pointer:
      slow_pointer = head
       while slow_pointer != fast_pointer:
          slow_pointer = slow_pointer.next
          fast_pointer = fast_pointer.next
        return fast_pointer
return 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.

自分もこのやり方をとりますね。
head とそれ以降のケースで NULL ではないことを判定するロジックを分ける必要はないかなと考えています。

```
8minくらい

- 最初pos(index)を返すものと勘違いしてごちゃごちゃしてしまった。> the `head` of a linked list, return _the node where the cycle begins_) と明記してあったが、見逃している。返り値の型も見逃していて2敗。そもそもの英語力が足りていない話もある。
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.

お互い頑張りましょう

```python
class Solution:
def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]:
if head is None or head.next is 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.

これ、ノード1個の時のnextがNoneの場合を弾いてるんですね。勉強になります。

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.

関数上ではhead = Noneもありえて、None.nextは動かないのでそれ予防ですね

head = head.next
slow_pointer = slow_pointer.next

return 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.

akmhmgc さんがすでにご指摘ですが、自分もheadを動かすのに抵抗があるので
node = head
などの代入を使ってもいいと思いました。

@yakataN yakataN requested a review from Copilot September 1, 2025 22:41
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR adds a comprehensive solution and explanation for the LeetCode problem "Linked List Cycle II" which requires finding the node where a cycle begins in a linked list.

  • Provides two different approaches: hash set method and Floyd's cycle detection algorithm (two pointers)
  • Includes detailed explanations of the mathematical reasoning behind the two-pointer approach
  • Documents the iterative problem-solving process across multiple steps

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.


class Solution:
def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]:
# ListNode -> indexとするdictをつくれればいけるのだが、こういう複雑な構造もkeyになれる、つまりHash化できるのかがわからない(多分できない)
Copy link

Copilot AI Sep 1, 2025

Choose a reason for hiding this comment

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

The comment contains inaccurate information. Python's ListNode objects are hashable by default (using object identity) and can be used as dictionary keys or set elements. The comment should be corrected to reflect that ListNode objects can indeed be hashed and used in sets/dictionaries.

Suggested change
# ListNode -> indexとするdictをつくれればいけるのだが、こういう複雑な構造もkeyになれる、つまりHash化できるのかがわからない(多分できない)
# ListNodeオブジェクトはPythonではデフォルトでハッシュ可能(オブジェクトのIDで)なので、setやdictのキーとして使える。

Copilot uses AI. Check for mistakes.
Comment on lines +69 to +71
Linked List Cycleでもあった、2 pointerを用いた方法も書こうとしたが、追いついたところが the head of a linked list になるわけではなく厳しい。slow_pointerの動いた数を保存しておいて、1つずつ前にずらせばどこかで答えに当たる。2分探索すればそれほどかからないが、正攻法ではないように思える。
考えてもわからなかったため、パス

Copy link

Copilot AI Sep 1, 2025

Choose a reason for hiding this comment

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

The explanation about binary search approach is unclear and potentially misleading. The comment suggests using binary search to find the cycle start, but this doesn't align with the standard Floyd's cycle detection algorithm that follows. Consider clarifying or removing this confusing explanation.

Suggested change
Linked List Cycleでもあった、2 pointerを用いた方法も書こうとしたが、追いついたところが the head of a linked list になるわけではなく厳しい。slow_pointerの動いた数を保存しておいて、1つずつ前にずらせばどこかで答えに当たる。2分探索すればそれほどかからないが、正攻法ではないように思える。
考えてもわからなかったため、パス
Linked List Cycleでもあった、2 pointerを用いた方法(Floydの循環検出アルゴリズム)が標準的な解法である。追いついた場所からheadとslow_pointerを同時に進めていくことで、サイクルの開始点を特定できる。

Copilot uses AI. Check for mistakes.
- fast_pointerが辿った道のり = x + y + z + y
- ここで、fast_pointerが辿った道のり = slow_pointerが辿った道のり* 2であることから、
- x = z
- よって、headとslowを進めて、邂逅した場所が the head of a linked list.
Copy link

Copilot AI Sep 1, 2025

Choose a reason for hiding this comment

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

The term '邂逅した場所が the head of a linked list' is confusing. It should clarify that the meeting point of head and slow pointer is 'the start of the cycle' rather than 'the head of a linked list'.

Suggested change
- よって、headとslowを進めて、邂逅した場所が the head of a linked list.
- よって、headとslowを進めて、邂逅した場所がサイクルの開始点(the node where the cycle begins)となる。

Copilot uses AI. Check for mistakes.
Comment on lines +32 to +33
if not is_cycled:
return 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.

この部分、is_cycled = False のところで return None すれば、フラグを一つ消せますね。

この順序の入れ替えの部分を見ておいていただけますか。
https://docs.google.com/document/d/11HV35ADPo9QxJOpJQ24FcZvtvioli770WWdZZDaLOfg/edit?tab=t.0#heading=h.4qnnfvpo8ij5

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants