-
Notifications
You must be signed in to change notification settings - Fork 0
141 linked list cycle #2
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
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,53 @@ | ||
| # 141. Linked List Cycle | ||
|
|
||
| <https://leetcode.com/problems/linked-list-cycle/description/> | ||
|
|
||
| ## step1(まず通す) | ||
|
|
||
| イメージとしては「速いやつと遅いやつ2人が同じ道を同じ方向に進んで、速いやつが追いついたらループしてる、そうでないならループしてない」(もともと知っていた) | ||
|
|
||
| fast は fast.next が None の場合に注意してないと None.next をやってエラーが出る | ||
|
|
||
| やったミス: | ||
|
|
||
| - 進めるのと判定するのの順序を間違えて全部 head == head で True になる | ||
| - fast を一つしか進めない | ||
|
|
||
| ↑頭の中で挙動をシミュレートする力が弱っているので鍛え直す必要がある。 | ||
|
|
||
| ## step2(整形&他の人のコードを読む) | ||
|
|
||
| ### 他のコード見る前 | ||
|
|
||
| head , head.next, head.next.next が None の場合は早期リターンして | ||
| slow, fast を始めから1ステップ進めた状態で定義、ループの中では判定→進めるの順でやることも考えたけど、同じような条件式が2回出てくるのは見づらいだろうということで Step1のスタイルにした。 | ||
|
|
||
| ### 見たあと | ||
|
|
||
| 参考になったコード・コメント・ドキュメント | ||
|
|
||
| - [他の方のメモ](https://github.com/naoto-iwase/leetcode/pull/1/changes/BASE..21cd6c5ca124e1b81b91b8003681e1a49b1c5927#diff-bb5a355bc7de70630b57c0d17d649e9adce45b5f0965089f22bf15dd4236d47fR75) | ||
| - [公式ドキュメント](https://docs.python.org/ja/3.13/library/stdtypes.html#comparisons) に「あるクラスの同一でないインスタンスは、通常等価でないとされますが、そのクラスが __eq__() メソッドを定義している場合は除きます。」「is および is not 演算子の振る舞いはカスタマイズできません。また、これらはいかなる 2 つのオブジェクトにも適用でき、決して例外を送出しません。」とあり、`==` は「(勝手に定義した)何らかの意味」で等しいというふうにできるのでオブジェクトが同一か知りたい今回は `is` を使うべき | ||
| - <https://github.com/naoto-iwase/leetcode/pull/1#discussion_r2388035509> | ||
| - 気になるのは「fast は slow に追いついたか?」なので、fast is slow としたい | ||
| - <https://discord.com/channels/1084280443945353267/1195700948786491403/1195944696665604156> | ||
| - 知ってたので Floyd の方法でやったが、「今見ているノードはすでに到達したか?」と考えて set でやる方が応用の効く(普通の)発想かもしれない | ||
| - set はハッシュマップだったはずなので `in` の判定も(衝突がなければ)O(1)だし、リストを線形に舐めるだけなのは変わらなそう | ||
| - [公式ドキュメント](https://docs.python.org/ja/3/library/stdtypes.html#set)にも「集合の要素は ハッシュ可能 なものでなくてはなりません」とあるし調べた感じそうらしいので間違いなさそう(本当は実装を読むべきなのでしょうが今日は時間がない) | ||
| - while の行に書く条件と while の中でループを抜けるために書く条件(少なくとも先頭か末尾なら)は、while を抜けるという意味で等価なのでどこに書くかのバリエーションが出る、という理解 | ||
| - 今回は、「終端に着くまで毎回『通った道じゃないよな?』と確認する」という気持ちだったので step2_set.py のように書いた | ||
| - <https://github.com/momeemt/LeetCode/pull/1/changes#r1569013430> | ||
| - Python では再起が好まれない話 | ||
| - デフォルトで深さが1000 | ||
|
Comment on lines
+40
to
+41
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. 言語ごとに制限があることが勉強になりました👀 |
||
| - 再帰的に定式化するとコードもそのまま書けるのでわかりやすい、という良さはあるがいつでも使えば良いってもんじゃない | ||
| - Python3 は末尾再帰最適化が効かないっぽい? | ||
| - [他の方のコード](https://github.com/shintaro1993/arai60/pull/3/changes#diff-491120a14a1981900662359b52a1c34fca42db491aff99fb79c9ba09193ba12fR109) | ||
| - fast が先に行ってくれており slow の next は None じゃないことが保証されているので `slow is not None` は書かなくても正しく動きはする | ||
| - ただしこれだと Mypy(型チェッカー) は赤線引いてくる(たぶん単純な型推論だとわからなくて、コードの内容くわしく見ないといけないから) | ||
|
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. step4 書いてみたんですが、 while ループの冒頭で assert しておくのが型検査も満足させながら人間的にも意味が分かりやすくて良かったりしますかね? 例えば Google のスタイルガイドだと以下のように書かれてます。
今回はもともと常に成り立つ条件を型検査機に教えてあげるコメントのような目的であり "the assert could be removed without breaking the code" に当てはまるので書いても良さそう、という感じです。(無視するコメントより情報量が多いという利点もありそうです) 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. step4のコードもスタイルガイドもありがとうございます! 共有していただいたスタイルガイドを読むと、Noの例で以下のコードが紹介されており、型チェックのためのassertはスタイルガイドでも推奨されていないかもしれません。 assert port is not None
# The type checking of the return statement relies on the assert.
return portまた、以下によると型チェックを無視することは選択肢として認める方針ではあるようです。 2.4.4と3.19.7しか読んでいませんが、型を守るためだけにプロダクションのコードに手を加えることは避けたほうが良いぐらいの感覚なのかなと思いました。 以下の観点おっしゃるとおりだなと思いました。
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. 具体例のところ見落としてました、、 注意深く読んでなくて勘違いしていましたが、型チェックを無視+その理由をコメントするという最初に仰っていたものが今回は一番しっくりきそうですね。(あるいは、冗長なのを承知で ありがとうございます! |
||
| - どっちが好まれる? | ||
| - 一応チェックない方が O(n) の定数倍が削れるし単純に `and` で繋ぐ数が少なくて見やすい気がする | ||
|
|
||
| ## step3(10分以内にさっとかける * 3回) | ||
|
|
||
| 個人的に書いてて「自然」に感じられるのは set 使う方、という感想。 | ||
| Floyd の方は「へーこんなやり方が」って感じな一方 set 使う方は「まあそうだよな」って感じで、おそらく後者の方がいい場面が多いので。 | ||
|
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. ですね。リストがめちゃくちゃ長い時は Floyd のアルゴリズムが活きそうです。 |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| from typing import Optional | ||
|
|
||
|
|
||
| # Definition for singly-linked list. | ||
| class ListNode: | ||
| def __init__(self, x): | ||
| self.val = x | ||
| self.next = None | ||
|
|
||
|
|
||
| class Solution: | ||
| def hasCycle(self, head: Optional[ListNode]) -> bool: | ||
| # リストを先頭から一つずつ歩いていくやつと1つ飛ばし(2倍速)で歩いていくやつがいて | ||
| # 速いやつが遅いやつに追いついたらループしてる | ||
| # どこかで None が出てきたらループしてない(そこが終端) | ||
|
|
||
| slow = head | ||
| fast = head | ||
|
|
||
| while slow is not None and fast is not None and fast.next is not None: | ||
| slow = slow.next | ||
| fast = fast.next.next | ||
|
|
||
| if slow == fast: | ||
| return True | ||
|
|
||
| return False |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| from typing import Optional | ||
|
|
||
|
|
||
| # Definition for singly-linked list. | ||
| class ListNode: | ||
| def __init__(self, x): | ||
| self.val = x | ||
| self.next = None | ||
|
|
||
|
|
||
| class Solution: | ||
| def hasCycle(self, head: Optional[ListNode]) -> bool: | ||
| # リストを先頭から一つずつ歩いていくやつと1つ飛ばし(2倍速)で歩いていくやつがいて | ||
| # 速いやつが遅いやつに追いついたらループしてる | ||
| # どこかで None が出てきたらループしてない(そこが終端) | ||
|
|
||
| slow = head | ||
| fast = head | ||
|
Comment on lines
+17
to
+18
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. どうでも良い好みなのですが、fastが先でslowが後というアルゴリズムでfastの方がNoneチェックやslowとの比較でアルゴリズムの軸になっている感があるので、fast, slowの定義順だとwhile文を1ループ目で読むときなど色々スッキリ頭に入って着やすい気がしました。
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. 確かにそうですね。
私も言われてみればそうかもしれません |
||
|
|
||
| while fast is not None and fast.next is not None: | ||
| slow = slow.next | ||
| fast = fast.next.next | ||
|
|
||
| if fast is slow: | ||
| return True | ||
|
|
||
| return False | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| from typing import Optional | ||
|
|
||
|
|
||
| # Definition for singly-linked list. | ||
| class ListNode: | ||
| def __init__(self, x): | ||
| self.val = x | ||
| self.next = None | ||
|
|
||
|
|
||
| class Solution: | ||
| def hasCycle(self, head: Optional[ListNode]) -> bool: | ||
| current_node = head | ||
| visited_nodes = set() | ||
|
|
||
| while current_node is not None: | ||
| visited_nodes.add(current_node) | ||
| current_node = current_node.next | ||
|
|
||
| if current_node in visited_nodes: | ||
| return True | ||
|
|
||
| return False |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| from typing import Optional | ||
|
|
||
|
|
||
| # Definition for singly-linked list. | ||
| class ListNode: | ||
| def __init__(self, x): | ||
| self.val = x | ||
| self.next = None | ||
|
|
||
|
|
||
| class Solution: | ||
| def hasCycle(self, head: Optional[ListNode]) -> bool: | ||
| slow = head | ||
| fast = head | ||
|
|
||
| while fast is not None and fast.next is not None: | ||
| slow = slow.next | ||
| fast = fast.next.next | ||
|
|
||
| if fast is slow: | ||
| return True | ||
|
|
||
| return False |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| from typing import Optional | ||
|
|
||
|
|
||
| # Definition for singly-linked list. | ||
| class ListNode: | ||
| def __init__(self, x): | ||
| self.val = x | ||
| self.next = None | ||
|
|
||
|
|
||
| class Solution: | ||
| def hasCycle(self, head: Optional[ListNode]) -> bool: | ||
| current_node = head | ||
| visited_nodes = set() | ||
|
|
||
| while current_node is not None: | ||
| visited_nodes.add(current_node) | ||
| current_node = current_node.next | ||
|
|
||
| if current_node in visited_nodes: | ||
| return True | ||
|
Comment on lines
+17
to
+21
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. と書きつつ、自分が解いたときはMA-yo-TA sanと同じ書き方をしたのでなんでだろうと振り返ると、おそらく最初の数要素分のループを実際に回すシミュレーションをしたときに、1つめの要素でvisited_nodesが空だとわかっているのに存在チェックするのが気持ち悪くて、nextの存在チェックをする書き方になるのかなぁという気持ちになりました。 好みだとは思いますが、アルゴリズム的には存在チェック→SetにNode追加→次のNodeに移動が自然な気が今はしています。
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.
自分も同じでしたが、言葉で説明するときは
なので素直に書いた方がいいかもしれないですね。 |
||
|
|
||
| return False | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| from typing import Optional | ||
|
|
||
|
|
||
| # Definition for singly-linked list. | ||
| class ListNode: | ||
| def __init__(self, x): | ||
| self.val = x | ||
| self.next = None | ||
|
|
||
|
|
||
| class Solution: | ||
| def hasCycle(self, head: Optional[ListNode]) -> bool: | ||
| # リストを先頭から一つずつ歩いていくやつと1つ飛ばし(2倍速)で歩いていくやつがいて | ||
| # 速いやつが遅いやつに追いついたらループしてる | ||
| # どこかで None が出てきたらループしてない(そこが終端) | ||
|
|
||
| slow = head | ||
| fast = head | ||
|
|
||
| while fast is not None and fast.next is not None: | ||
| assert slow is not None | ||
|
|
||
| slow = slow.next | ||
| fast = fast.next.next | ||
|
|
||
| if slow == fast: | ||
| return True | ||
|
|
||
| return False |
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.
Javaでも等値と等価という似たような話があったので、これは結構気をつけないと駄目なところだなぁと感じています。