Skip to content

141. Linked List Cycle #12

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

Merged
merged 2 commits into from
Jan 30, 2025
Merged
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
113 changes: 113 additions & 0 deletions 0141_Linked_List_Cycle/solution.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
## Problem
https://leetcode.com/problems/linked-list-cycle/

## Step 1
5分程度答えを見ずに考えて、手が止まるまでやってみる。
何も思いつかなければ、答えを見て解く。ただし、コードを書くときは答えを見ないこと。
動かないコードも記録する。
正解したら一旦OK。思考過程もメモする。

### Approach
* 素直に手でやるならどうかと考えたやり方
* ListNodeを1つずつ走査しながらHashSetに訪問済のListNodeを順次格納していき、新たに訪問したListNodeがSetの中にあったらtrue
* 空間計算量がO(n)と高いので、もっといい方法ありそうと思いながら書いた

```java
public class Solution {
public boolean hasCycle(ListNode head) {
ListNode current = head;
HashSet<ListNode> visitedSet = new HashSet<>();

while (current != null) {
if (visitedSet.contains(current)) {
return true;
}
visitedSet.add(current);
current = current.next;
}
return false;
}
}
```

## Step 2
他の方が描いたコードを見て、参考にしてコードを書き直してみる。
参考にしたコードのリンクは貼っておく。
読みやすいことを意識する。
他の解法も考えみる。

### 他の方のPR
* https://github.com/onyx0831/leetcode/pull/1
* https://github.com/t0hsumi/leetcode/pull/1
* https://github.com/ryuryu5121/Arai60/pull/1
* https://github.com/hajimeito1108/arai60/pull/1
* https://github.com/lilnoahhh/leetcode/pull/1


### 解法1. フロイドの循環検出法
* 1つ飛ばしで進むウサギ(fast)と1つずつ進む亀(slow)の2つのポインタを同時に同じリスト上を走らせると、サイクルがある場合必ずどこかのノードで合流するという考え方
* https://en.wikipedia.org/wiki/Cycle_detection#Floyd's_tortoise_and_hare
* 感想: 2つ走らせるという発想はなかった。時間をかけても自分でゼロから思いつくのは厳しいと思う。考え方のパターンとしてストックしたい
* [追記]レビュアーからのコメントとして、この解法は科学手品のようなものなので自分で思いつけなくてもあまり気にしなくてよいとのこと。一方でStep1のSetを使った解法は思いつけて普通とのこと

Choose a reason for hiding this comment

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

全体的に良いと思います。Discord内に直感的な解説も転がってるので、まだ見てなければどうぞ。
https://discord.com/channels/1084280443945353267/1246383603122966570/1252209488815984710

Copy link
Owner Author

Choose a reason for hiding this comment

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

非常に分かりやすいですね。とても参考になりました。
ありがとうございます。


```java
public class Solution {
public boolean hasCycle(ListNode head) {
ListNode fast = head
ListNode slow = head;

while (fast != null && fast.next != null) { // Check the lead runner fast.
fast = fast.next.next;
slow = slow.next;
if (fast == slow) {
return true;
}
}
return false;
}
}
```
### 解法2. 訪問済NodeをSetに格納する方法
* Step1 と同じものだが、current という命名がわかりづらいという事がたびたび議論になっているため、nodeに変更
* https://github.com/katsukii/leetcode/pull/12#discussion_r1932786013
* また、変数名に型名を入れるのはよくないため、 visitedSet -> visitedNodes に変更

```java
public class Solution {
public boolean hasCycle(ListNode head) {
ListNode node = head;
HashSet<ListNode> visitedNodes = new HashSet<>();
Comment on lines +75 to +79

Choose a reason for hiding this comment

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

レビュー遅くなりすみません。

全体的に良いと思います!ただすでに指摘されているように変数名が気になりました。visitedNodesのように変数名に大文字を含むのはpepでは推奨されていないようです。
そのためvisited_nodesなどのようにするほうがいいと思いますが、そもそもnodeが入っていないときもあるので、僕だったらvisitedくらいにします。

関数や変数の名前
関数の名前は小文字のみにすべきです。また、読みやすくするために、必要に応じて単語をアンダースコアで区切るべきです。
変数の名前についても、関数と同じ規約に従います。
mixedCase が既に使われている (例: threading.py) 場合にのみ、互換性を保つために mixedCase を許可します。

https://pep8-ja.readthedocs.io/ja/latest/#id35

Choose a reason for hiding this comment

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

おそらく別のドキュメントですが、仰る通り同じく、PEP8 という規約では変数名と関数名は lower_case_with_underscores を使いましょうとなっているようです。
https://peps.python.org/pep-0008/#descriptive-naming-styles

Copy link
Owner Author

Choose a reason for hiding this comment

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

@lilnoahhh @olsen-blue
レビューありがとうございます。たしかにvisitedの方が良さそうですね。

変数名の命名規則に関してですが、PEP8はPython向けの規約のため一概にJavaのコードに適用すべきではないと考えています。以下はGoogleのコーディング規約ですが、JavaではlowerCamelCaseが推奨されているようです。

Local variable names are written in lowerCamelCase

https://google.github.io/styleguide/javaguide.html#s5.2.7-local-variable-names

Choose a reason for hiding this comment

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

その通りですね、失礼しました


while (node != null) {
if (visitedNodes.contains(node)) {
return true;
}
visitedNodes.add(node);
node = current.next;
}
return false;
}
}
```

## Step 3
今度は、時間を測りながら、もう一回書く。
アクセプトされたら消すを3回連続できたら問題はOK。

```java
public class Solution {
public boolean hasCycle(ListNode head) {
ListNode fast = head
ListNode slow = head;

while (fast != null && fast.next != null) { // Check the lead runner fast.
fast = fast.next.next;
slow = slow.next;
if (fast == slow) {
return true;
}
}
return false;
}
}
```