Skip to content

Create 142. Linked List Cycle II.md#2

Open
wanwan87 wants to merge 4 commits into
mainfrom
wanwan87-Linked-List-Cycle-2
Open

Create 142. Linked List Cycle II.md#2
wanwan87 wants to merge 4 commits into
mainfrom
wanwan87-Linked-List-Cycle-2

Conversation

@wanwan87
Copy link
Copy Markdown
Owner

@wanwan87 wanwan87 commented Apr 6, 2026

Line 71 in <module> (Solution.py)
```

returnでprintを返すのかな?
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

return で print を返すという表現に違和感を感じました。

参考までに、関数の return 文で、 print 関数を返すとこのようになります。

C:\Program Files\Microsoft Visual Studio\2022\Community>python
Python 3.12.10 (tags/v3.12.10:0cc8128, Apr  8 2025, 12:21:36) [MSC v.1943 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> def f():
...     return print
...
>>> f()
<built-in function print>

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.

ありがとうございます、問題の理解不足で変な操作をしようとしていました。
printのオブジェクトがそのまま返ってしまうのですね

visited = []
node = head
while node is not None:
if node in 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.

list に特定の値が含まれているかを確認するとき、すべての要素を一つ一つ調べなければなりません。これには時間がかかります。時間計算量は O(n) となります。

set を使用すると、特定の値が含まれているかを確認するとき、高速に調べることができます。時間計算量は O(1) となります。

配列・ハッシュテーブルあたりのデータ構造について調べ、余裕があれば CPython の set の実装を参考にされるとよいと思います。

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://qiita.com/alswnd/items/428014509e52297b3a1b


→AI回答「node = head に代入する理由head を直接動かすと、元のリストの先頭への参照を失うためです。node という別変数を使って走査します。」

→まだ参照とかをちゃんと理解しきれていなさそう。node = headのタイミングではnodeとheadは同じオブジェクト。node = node.nextとしたタイミングで右辺の計算結果のオブジェクトへの参照がnodeに入る。headはListNodeのオブジェクトだが、head.valは具体的な数値、head.nextは他のlistnodeへの参照。元のリストの先頭への参照は慣習的に残しているっぽい。
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.valもintのオブジェクトですよ。変数は全てオブジェクトへの参照です。

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.

ありがとうございます、認識を改めます

Copy link
Copy Markdown

@enari-k enari-k left a comment

Choose a reason for hiding this comment

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

set を使った実装、お疲れ様です!ロジックも合っていますし、まずは確実に動く解法を書けることが素晴らしいです。

実はこの問題、下の方に「空間計算量 $O(1)$ にできますか?」というフォローアップの問いかけがあるのですが、ぜひそっちのアルゴリズムも実装してみることをお勧めします。

以前、元googleのエンジニアにコーディングの模擬面接をしてもらったことがあるのですが、一つ解法を書いた後にそれより良い解法があるならば、たとえば今回の場合だと、空間計算量O(1)で書けますか?と聞いてくるからです。

以下、空間計算量O(1)で書く場合のアルゴリズムについての証明とコードを載せておきます。

証明

fastとslowの二つのポインターを用意して、fastを2歩ずつ、slowを1歩ずつ進めます。
すると、この二つがいずれ衝突します。ここで、
H:ループの開始地点
L:一ループの長さ
D:ループの開始地点から衝突地点まで
のように定義します。
fast = H + D + n * L
slow = H + D
となるはずです。ここで、fast = slow * 2が成立することから
H + D + n * L = 2 * (H + D)
より整理すると、
H = n * L - D
H = (n - 1) * L + (L - D)
となる。上記の式から先ほどの衝突地点から、H歩進んだ時に、ちょうどループしてループの開始地点に戻ることが分かります。よって、開始地点からH歩進ませるのとさっきの衝突地点からH歩進ませたものはちょうどループの開始地点で衝突するはずです。これによって、ループの開始地点は特定できます。

このアルゴリズムを用いることにより、時間計算量はO(N)、空間計算量はO(1)となります。

コード

class Solution:
    def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]:
        fast = head
        slow = head
        isCycle = False
        
        if head is None:
            return None
            
        while fast.next is not None and fast.next.next is not None:
            fast = fast.next.next
            slow = slow.next
            if fast == slow:
                isCycle = True
                break
                
        if not isCycle:
            return None
            
        slow = head
        while fast != slow:
            slow = slow.next
            fast = fast.next
            
        return fast

@wanwan87
Copy link
Copy Markdown
Owner Author

set を使った実装、お疲れ様です!ロジックも合っていますし、まずは確実に動く解法を書けることが素晴らしいです。

実はこの問題、下の方に「空間計算量 O ( 1 ) にできますか?」というフォローアップの問いかけがあるのですが、ぜひそっちのアルゴリズムも実装してみることをお勧めします。

以前、元googleのエンジニアにコーディングの模擬面接をしてもらったことがあるのですが、一つ解法を書いた後にそれより良い解法があるならば、たとえば今回の場合だと、空間計算量O(1)で書けますか?と聞いてくるからです。

以下、空間計算量O(1)で書く場合のアルゴリズムについての証明とコードを載せておきます。

証明

fastとslowの二つのポインターを用意して、fastを2歩ずつ、slowを1歩ずつ進めます。 すると、この二つがいずれ衝突します。ここで、 H:ループの開始地点 L:一ループの長さ D:ループの開始地点から衝突地点まで のように定義します。 fast = H + D + n * L slow = H + D となるはずです。ここで、fast = slow * 2が成立することから H + D + n * L = 2 * (H + D) より整理すると、 H = n * L - D H = (n - 1) * L + (L - D) となる。上記の式から先ほどの衝突地点から、H歩進んだ時に、ちょうどループしてループの開始地点に戻ることが分かります。よって、開始地点からH歩進ませるのとさっきの衝突地点からH歩進ませたものはちょうどループの開始地点で衝突するはずです。これによって、ループの開始地点は特定できます。

このアルゴリズムを用いることにより、時間計算量はO(N)、空間計算量はO(1)となります。

コード

class Solution:
    def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]:
        fast = head
        slow = head
        isCycle = False
        
        if head is None:
            return None
            
        while fast.next is not None and fast.next.next is not None:
            fast = fast.next.next
            slow = slow.next
            if fast == slow:
                isCycle = True
                break
                
        if not isCycle:
            return None
            
        slow = head
        while fast != slow:
            slow = slow.next
            fast = fast.next
            
        return fast

@enari-k
ありがとうございます!もう1つ解法があることは認識してましたが証明部分はちゃんと理解していなかったのでAIと壁打ちしながらなんとか理解できました。

一つ解法を書いた後にそれより良い解法があるならば

→視野や選択肢を広げるためにはほかの人のPRなど見ながら吸収していこうと思います

コードもありがとうございます!

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.

4 participants