-
Notifications
You must be signed in to change notification settings - Fork 0
Create 0142-linked-list-cycle-ii.md #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,120 @@ | ||
| ## Step 1 | ||
|
|
||
| 前回の問題 linked-list-cycle で、返り値が複雑になった問題。 | ||
| ひとまずset()で訪問済みを管理するやり方なら返り値を変えるだけでいいので、実装。 | ||
|
|
||
| - 時間計算量: O(n) | ||
| - 空間計算量: O(n) | ||
|
|
||
| ```python3 | ||
| # Definition for singly-linked list. | ||
| # class ListNode: | ||
| # def __init__(self, x): | ||
| # self.val = x | ||
| # self.next = None | ||
|
|
||
| class Solution: | ||
| def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]: | ||
| node = head | ||
| visited = set() | ||
| while node is not None: | ||
| if node in visited: | ||
| return node | ||
| visited.add(node) | ||
| node = node.next | ||
| return None | ||
| ``` | ||
|
|
||
| ... is not Noneは暗黙的falseを嫌った書き方。 | ||
|
|
||
| ここまで3分。 | ||
|
|
||
| さて、予想できていたが、以下、 | ||
| > Follow up: Can you solve it using O(1) (i.e. constant) memory? | ||
|
|
||
| Floydの方法の拡張だと思うが、脳内シミュレーションをしても、出会う場所はリストの長さとサイクルの長さによってまちまち。 | ||
| わからないので、GPT-5にヒントをもらう。 | ||
|
|
||
| > * まず **slow**(1歩ずつ)と **fast**(2歩ずつ)を動かすと、ループがある場合いつか同じ場所で会う。 | ||
| > * その後、**一方を head に戻して、両方を 1 歩ずつ進める**と、次に会う場所がループの入口になる。 | ||
|
|
||
| おそらく、リストの長さとサイクルの長さを文字で置いて証明できる気がする。 | ||
|
|
||
| ひとまずはこのアルゴリズムを実装する。 | ||
|
|
||
| - 時間計算量: O(n) | ||
| - 空間計算量: O(1) | ||
|
|
||
| ```python3 | ||
| class Solution: | ||
| def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]: | ||
| slow = head | ||
| fast = head | ||
| meet = None | ||
| late = head | ||
| while fast is not None and fast.next is not None: | ||
| fast = fast.next.next | ||
| slow = slow.next | ||
| if fast is slow: | ||
| meet = fast | ||
| break | ||
| if meet is None: | ||
| return None | ||
| while late is not meet: | ||
| late = late.next | ||
| meet = meet.next | ||
| return late | ||
| ``` | ||
|
|
||
| 変数名、meetはいいけどlateはこれでよかったのかな、という気持ちが残る。 | ||
|
|
||
| restartとかのがいいのか。でも本人はrestartしてないので違和感があり、遅刻的なニュアンスでlateにした。 | ||
|
|
||
| でもlateは遅刻というよりlatestのニュアンスで取られそう。他の方がどう命名されたか気になる点。 | ||
|
|
||
| ここまで16分。 | ||
|
|
||
| ## Step 2 | ||
|
|
||
| - https://github.com/TrsmYsk/leetcode/pull/2 | ||
| - Floydの方法の前半、whileの条件に、fastのNoneチェックを集約させた方が良さそう。 | ||
| - Floydの方法の後半、meet, lateの変数名に、from_start, from_meetingとしていて、fromが位置を元に命名していることを明確にしていていいなと思った。 | ||
| - ネストが深くなることを躊躇されていて、素敵だなと思った。 | ||
| - https://github.com/yas-2023/leetcode_arai60/pull/2 | ||
| - Floydの方法の後半、変数にslow, fastを使い回すと、何しているかが一見してわかりにくいという印象を持った。 | ||
| - https://github.com/Kaichi-Irie/leetcode-python/pull/20 | ||
| - Floydの方法の前半と後半で、変数の初期化を直前に行なっているのが、とてもわかりやすいなと思った。 | ||
| - 自分の実装は、最初に4つ全てを初期化しているが、読み手のワーキングメモリを食わせてしまう。 | ||
| - 自分の実装で、if fast is slow:が成り立つ瞬間meet=fastと慌てて代入しているが、別にfast(の位置)はすぐに失われないので、ここはフラグを保持すれば十分。 | ||
|
|
||
| ## Step 3 | ||
|
|
||
| ```python3 | ||
| class Solution: | ||
| def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]: | ||
| fast = head | ||
| slow = head | ||
| has_cycle = False | ||
| while fast is not None and fast.next is not None: | ||
| fast = fast.next.next | ||
| slow = slow.next | ||
| if fast is slow: | ||
| has_cycle = True | ||
| break | ||
|
Comment on lines
+95
to
+103
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よりも構造化されていないという意見もあったので、それも関数化することによって避けることができるかなと思います。
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. ありがとうございます。 確かに、 前半は関数として切り出すことで、前半の変数をもう使わない/操作しないと明示できるのはいいですね。 |
||
| if not has_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. コードの整え方のところを見ておいてください。要はよく使う変形があるんですよ。 |
||
| return | ||
|
|
||
| from_start = head | ||
| from_meeting = fast | ||
| while from_start is not from_meeting: | ||
| from_start = from_start.next | ||
| from_meeting = from_meeting.next | ||
| return from_start | ||
| ``` | ||
|
|
||
| no cycleでNoneを返すところ、冗長だと思ったので、 | ||
| ```python3 | ||
| if not has_cycle: | ||
| return | ||
|
Comment on lines
+115
to
+118
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. ここは明示的にNoneを返す方が分かりやすいかなと思いました。
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. ありがとうございます。 ですね、同意します。 |
||
| ``` | ||
| としてみました。 | ||
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.
数式での証明も可能ですが、Floydのアルゴリズムは自然言語でも直観的に説明できます。
https://discord.com/channels/1084280443945353267/1246383603122966570/1252209488815984710
しかし、実際の面接におけるFloydのアルゴリズムの出題意図はおおむね次のようなものらしいので、以上の説明を自分で思いつけなくても特に問題はないと思います。
pineappleYogurt/leetCode#3 (comment)
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.
補足ありがとうございます!
なるほどですね。常識を問う手段ではあるが、それ自体常識ではないってことですね。
「かめがスタート地点に戻った時、うさぎはどこにいるでしょうか。実は、うさぎは衝突点にいます。なぜかというと、うさぎは倍速で走っているからです。スタート地点から衝突点を通って衝突点に到達するうさぎルートの長さは、スタートから衝突点に到達するかめルートの2倍だからです。」
の部分が前文から飛躍しているように感じられました。
少し調べ、「亀が歩いた距離がサイクル長の整数倍なので、」という行間を埋めることで理解に達しました。
パズルとして面白かったです。
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.
あれ、「亀が歩いた距離がサイクル長の整数倍なので、」は論理に必要ですか。
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.
ありがとうございます。
いやたしかに、その衝突点で衝突した(=両者の歩いた距離にサイクル長の整数倍の差が生じた)という事実から簡単に導けますね。
ここはそういう意味ですね。