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
31 changes: 31 additions & 0 deletions arai60/linked-list-cycle/memo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# 141. Linked List Cycle

## Link

https://leetcode.com/problems/linked-list-cycle/

## How to work on each step

- Step 1: 答えを見ずに 5 分以内に解く。わからなかったら答えを見て、開始から答えを見ないで 5 分以内に正解になるところまで行う。
- Step 2: 本協会メンバーや LeetCode の過去解答を参考にしつつ、コードを見やすくする形で整える。
- Step 3: 全部消して、10 分以内にエラーを一度も出さずに正解するのを 3 回続けて行う。
- Step 4: いただいたレビューをもとに、コードを整える。

## Comments

### Step 1
5分以内にはまったく解けなかった。
なんとなく一度訪れた値を保存しておいて、再度訪れたら true を返すイメージだけ思いついたが、表現方法が分からずタイムアウト。

調べると自分がイメージしていたのは、再帰関数で実現出来そうだったので、再帰関数で対応してみた。
Copy link

Choose a reason for hiding this comment

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

再帰で書くことはとてもよいことですが、再帰は深さの限界があることが多いので状況からして問題がないかを見積もりましょう。
もちろん、他にもいろいろなよしあしがあるので徐々に意識していきましょう。
https://docs.google.com/document/d/11HV35ADPo9QxJOpJQ24FcZvtvioli770WWdZZDaLOfg/edit?tab=t.0#heading=h.uvguf4c3q02d

最終的には、どのやりかたもいいところ悪いところがあるが、その中ではこれが今のところよいかなと思えるようになることです。

Copy link
Owner Author

@yosukekato165 yosukekato165 Aug 24, 2025

Choose a reason for hiding this comment

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

@oda
コメントありがとうございます!

LeetCode 141: Linked List Cycle の制約

  • ノード数: 0 ≤ n ≤ 10^4
  • 最悪ケース: 10,000ノードの一直線リスト

上記の場合は深さは最大O(10^4)になる認識です。
調べたところ、leetcodeの深さは最大550000まで大丈夫なようなのでleetcode上では大丈夫そうですが、
ローカルのMacの場合1000が限界と言う内容もあるのでptyhonでの再帰は現実的ではないのかな?と思いました。

https://www.reddit.com/r/leetcode/comments/12xy7f9/extraordinarily_high_system_recursion_limit_for/?tl=ja

Choose a reason for hiding this comment

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

何を見たか、リファレンスへのリンクがあると比較しやすいかなと思いました。

Copy link
Owner Author

Choose a reason for hiding this comment

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

@brood0783
コメントありがとうございます!!
今回はAIに聞いて出てきたものだったので次からリンク共有出来るようにします!


### Step 2

調べたらTwo pointer (フロイドのうさぎとかめ)と言うアルゴリズムを見つけた。
ただ、[常識ではない](https://discord.com/channels/1084280443945353267/1195700948786491403/1195944696665604156)らしい。
個人的にも初見でこれを見たら実装者に質問しそうなので再帰関数を選択することにした。

再帰関数は内部関数を無くすことでよりシンプルな実装に変更。

### Step 3
ここまでで頭の中が整理されたようで意外とすんなりStep3は通過した。
34 changes: 34 additions & 0 deletions arai60/linked-list-cycle/step1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Definition for singly-linked list.
# class ListNode(object):
# def __init__(self, x):
# self.val = x
# self.next = None

class Solution(object):
def hasCycle(self, head):
def depthFirstSearch(node, visited):

Choose a reason for hiding this comment

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

inner function とするなら、visited は引数に取らず、hasCycle のスコープで定義すればよいと思います

Copy link
Owner Author

Choose a reason for hiding this comment

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

@ryosuketc
コメントありがとうございます!
確かにです!
出来るだけ純粋関数にしたかったんですが、inner function であれば関係ありませんでした。
d65d37d

Choose a reason for hiding this comment

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

手法を関数名にすることはあまりないので、has_cycle_helper などの命名が無難かなと思います。

Copy link
Owner Author

Choose a reason for hiding this comment

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

@ryosuketc
手法を関数名にすることはないんですね!
アルゴリズムを学習するのが初めてなので知らない人にも分かりやすいかなぁと思ったんですが、
「常識として知っているべきだから手法を明示する必要はない」と言うことなのでしょうか?
それとも手法は別の方法で明示するべきなのでしょうか?
d65d37d

Copy link

Choose a reason for hiding this comment

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

書類の整理のときにフォルダーに書く名前は「平成25年度取締役会議事録」などであって、中の構造、たとえば「時系列順」ではないですよね。
関数の名前は、呼んでいるときに中身を見ずに、入力から何が出てくるかが分かるといいです。

student_names = recursion(student_names)

こう書いてあったら recursion の中を解読に行きますね。探索が一段階で済めばいいですけど。

student_names = sorted(student_names)

これ想像がつきますね。
https://docs.google.com/document/d/11HV35ADPo9QxJOpJQ24FcZvtvioli770WWdZZDaLOfg/edit?tab=t.0#heading=h.fcs3httrll4l

Copy link
Owner Author

@yosukekato165 yosukekato165 Aug 24, 2025

Choose a reason for hiding this comment

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

@oda
コメントありがとうございます!

関数の名前は、呼んでいるときに中身を見ずに、入力から何が出てくるかが分かるといいです。

確かにです!
関数を使う側としては、内部の処理よりも何を返してもらえるか。の方が重要ですね!
でないと、内部の処理を読んでからでないと必要な値を返してくれるか判断つかずコードリーディングに時間掛かりますし、何よりストレスが凄そうだなぁと思いました!

if not node:
return False
if node in visited:
return True

visited.add(node)
return depthFirstSearch(node.next, visited)

return depthFirstSearch(head, set())
Comment on lines +7 to +18

Choose a reason for hiding this comment

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

たしかに再帰で書けますね。思いつかなかったので、おもしろいな〜と思いました。


#### レビュー反映

class RevisedSolution(object):
def hasCycle(self, head):
visited = set()
def has_cycle_helper(node):
if not node:
return False
if node in visited:
return True

visited.add(node)
return has_cycle_helper(node.next)

return has_cycle_helper(head)
61 changes: 61 additions & 0 deletions arai60/linked-list-cycle/step2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Definition for singly-linked list.
# class ListNode(object):
# def __init__(self, x):
# self.val = x
# self.next = None

class TwoPointerSolution(object):
def hasCycle(self, head):
if not head or not head.next:
return False

slow = head
fast = head.next

while slow != fast:

Choose a reason for hiding this comment

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

今回の場合、==!=で確認される値の等価性ではなく、slowとfastが同じオブジェクトを指しているかどうかをチェックしたいので、slow is not fastの方が適切かと思います。
slow != fastslow.__ne__(fast)の構文糖であり、TreeNodeにそのspecial methodが定義されていなければ、is notと同じ結果が返るのですが、TreeNodeに__ne__()の定義があった場合、この条件式が予期せぬ値を返すことが予想されます。LeetCodeのTreeNodeの定義では大丈夫なのですが、is notだと読み手にこのようなことを心配させないメリットがあります。

Copy link
Owner Author

Choose a reason for hiding this comment

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

@huyfififi
コメントありがとうございます!

!=slow.__ne__(fast)の構文糖であるのは驚きでした!
普段TypeScriptを書いているので当たり前のように使用していたのですが、Ptyhonでは構文糖であり書き換えが可能なんだと理解しました!
d65d37d

if not fast or not fast.next:

Choose a reason for hiding this comment

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

notはfalsy判定なので、is Noneの方が安全かなと思いました。
https://stackoverflow.com/questions/39983695/what-is-truthy-and-falsy-how-is-it-different-from-true-and-false

Copy link
Owner Author

Choose a reason for hiding this comment

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

@brood0783
not が falsy 判定だったんですね!修正しました!
d65d37d

return False
slow = slow.next
fast = fast.next.next

return True

class RecursionSolution(object):

Choose a reason for hiding this comment

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

これは再帰ではないですね

Copy link
Owner Author

Choose a reason for hiding this comment

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

@ryosuketc
本当ですね、、
再帰とループに混乱していました。
コメントありがとうございます!
d65d37d

def hasCycle(self, head):
visited = set()
node = head
while node:
if node in visited:
return True
visited.add(node)
node = node.next
return False

#### レビュー反映

class RevisedTwoPointerSolution(object):
def hasCycle(self, head):
if head is None or head.next is None:
return False

slow = head
fast = head.next

while slow is not fast:
if fast is None or fast.next is None:
return False
slow = slow.next
fast = fast.next.next

return True

class HashSetSolution(object):
def hasCycle(self, head):
visited = set()
node = head
while node:
if node in visited:
return True
visited.add(node)
node = node.next
return False
23 changes: 23 additions & 0 deletions arai60/linked-list-cycle/step3.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
class RecursionSolution(object):
def hasCycle(self, head):
visited = set()
node = head
while node:
if node in visited:
return True
visited.add(node)
node = node.next
return False

#### レビュー反映

class HashSetSolution(object):
def hasCycle(self, head):
visited = set()
node = head
while node:
if node in visited:
return True
visited.add(node)
node = node.next
return False
24 changes: 24 additions & 0 deletions arai60/linked-list-cycle/step4.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
class Solution(object):
def hasCycle(self, head):
visited = set()
node = head
while node:
Copy link

@huyfififi huyfififi Aug 24, 2025

Choose a reason for hiding this comment

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

broodさんがご指摘の通り、また私のa != ba.__ne__(b)であるというコメントも合わせて、私はwhile node is not None: の方が適切かと思っています。ここでしたいのはListNodeの真偽値チェックではなく、Noneではないかチェックであると思いますし、!= 同様 ListNode の定義次第ではこの条件式が予期せぬ挙動をするので。

class ListNode:
    def __init__(self, x):
        self.val = x
        self.next = None

    def __bool__(self):
        return self.val > 0


if ListNode(1):
    print(bool(ListNode(1)))  # True
if not ListNode(0):
    print(bool(ListNode(0)))  # False

Copy link
Owner Author

@yosukekato165 yosukekato165 Aug 24, 2025

Choose a reason for hiding this comment

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

@huyfififi

ここでしたいのはListNodeの真偽値チェックではなく、Noneではないかチェックであると思います、!= 同様 ListNode の定義次第ではこの条件式が予期せぬ挙動をするので。

コメントありがとうございます!
確かにです!

定義次第ではこの条件式が予期せぬ挙動をするので。

この「定義次第で条件式が予期せぬ挙動をする」部分が普段意識出来ておらず抜けがちなようです!
これから意識していこうと思います!

dd7b06a

if node in visited:
return True
visited.add(node)
node = node.next
return False

## 指摘反映

class Solution(object):
def hasCycle(self, head):
visited = set()
node = head
while node is not None:
if node in visited:
return True
visited.add(node)
node = node.next
return False