Skip to content
Open
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
112 changes: 112 additions & 0 deletions leetcode/arai60/memo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# 141. Linked List Cycle
* 問題リンク: https://leetcode.com/problems/linked-list-cycle/
* 言語: Python3

# Step1
## 方針
* 循環しているnodeのポインタ `pos` は引数として与えられない
* 循環を「既に訪れたnodeの値があるかどうか」で検出する
* 後述の問題は出ると思ったがとりあえず実装してみた

### すべてのテストケースを通過しないコード
```python
class Solution:
def hasCycle(self, head: Optional[ListNode]) -> bool:
if not hasattr(head, "next"):
return False

current_node = head
visited_node_val = []

while True:
if current_node.val in visited_node_val:
return True
if current_node.next is not None:
visited_node_val.append(current_node.val)
current_node = current_node.next
else:
return False
```

* 案の定、循環していなくても同じ値があれば循環と誤検出する
* 答えを考えて5分過ぎていたので正答を見る

### 正答
```python
class Solution:
def hasCycle(self, head: Optional[ListNode]) -> bool:
current_node = head
visited_node = set()

while current_node:
if current_node in visited_node:
return True
visited_node.add(current_node)
current_node = current_node.next

return False
```

* そもそもnodeの値(`current_node.val`)ではなく、nodeオブジェクトの参照の値(メモリ上のアドレス)を追加すれば(一意となるので)良い
* `while True` としない場合、以下は無くても動く
```python
if not hasattr(head, "next"):
return False
Comment on lines +53 to +54

Choose a reason for hiding this comment

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

そもそも Python で hasattr を使うこと自体黒魔術みがあるような気がして、個人的には避けたいのは私だけでしょうか…?一般的な感覚かどうかは自信がないです。
ここに限れば if head is None: のほうがよいかと思います。

Copy link

Choose a reason for hiding this comment

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

静的型付けに慣れてるからか同じくhasattr/getattrに苦手意識があります…
これがダックタイピングか!となりました。https://docs.python.org/ja/3/glossary.html#term-duck-typing

Copy link
Owner Author

Choose a reason for hiding this comment

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

正直ここは書いていてもう少し上手いやり方がありそうだとは思っていました。

```
* 時間計算量: $O(n)$
* 空間計算量: $O(n)$

# Step2
## 別解を読む
* https://github.com/ryosuketc/leetcode_arai60/pull/1
- 一回の走査で2つ先を読む「速いポインタ」と1つ先を読む「遅いポインタ」の2種類のポインタを用意して、その2つのポインタが指す位置が同じになるかどうかで循環検出を行う方法
- フロイドの循環検出法と呼ぶらしい
- アルゴリズム自体は単純だが、すぐに思いつかないアルゴリズムだと思った
- このアルゴリズムは「常識」の範囲内なのだろうか?

Choose a reason for hiding this comment

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

範囲外かと思います。
https://discord.com/channels/1084280443945353267/1246383603122966570/1251555126578118676

以下引用


いや、正直、私はこの問題はこちらの解答が標準だと思います。

Tortoise and Hare をその場で発見した場合、チューリング賞を取った Floyd 並に賢いです。
Tortoise and Hare を知っていた場合、知っていたところでどうでもいいです。これは科学手品みたいに子供の興味を引くときに使うものです。
Tortoise and Hare を知らなかった場合、特に何もありません。

上の set を使ったコードが書けた場合、普通はできます。
上の set を使ったコードが書けなかった場合、一緒に働くことが困難です。

というわけで、set を使えるかを判定している出題です。

- 変数の初期化順序
- https://docs.python.org/3/reference/simple_stmts.html#assignment-statements
- `!=` と `is not` の違い(値の比較かid checkか)

Choose a reason for hiding this comment

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

一応、id というか、同一メモリアドレス上かという理解の方が正確なのかなと思いました。最近こんななぞなぞにあたったので…。

https://discord.com/channels/1084280443945353267/1084283898617417748/1387536834468511947

Copy link

Choose a reason for hiding this comment

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

https://docs.python.org/3.13/reference/expressions.html#is

The operators is and is not test for an object’s identity: x is y is true if and only if x and y are the same object. An Object’s identity is determined using the id() function. x is not y yields the inverse truth value. [4]

https://docs.python.org/3/library/functions.html#id

Return the “identity” of an object. This is an integer which is guaranteed to be unique and constant for this object during its lifetime. Two objects with non-overlapping lifetimes may have the same id() value.
CPython implementation detail: This is the address of the object in memory.

メモリアドレスを表しているのはあくまでCPythonの実装の話なので、id checkという理解で良い気がします。

- https://docs.python.org/3/library/operator.html#mapping-operators-to-functions
- https://peps.python.org/pep-0008/#programming-recommendations

* https://github.com/mptommy/coding-practice/pull/1
- node数の上限が決まっているので、ループ回数がその上限を超えたら循環とみなす方法

Choose a reason for hiding this comment

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

問題の解法としてはあるかなと思うのですが、個人的に実際のソフトウェアの実装では使いたくないなと感じます。

- 問題として答えは一応出るが、正規表現のReDoSの検出っぽい方法だと思った

## フロイドの循環検出法で解く
```python
class Solution:
def hasCycle(self, head: Optional[ListNode]) -> bool:
fast_ptr = head
Copy link

Choose a reason for hiding this comment

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

単語から文字を削って変数名に付けると、読み手にとって認知負荷が上がる場合があります。できるだけ避けたほうがよいと思います。

参考までにスタイルガイドへのリンクを貼ります。

https://google.github.io/styleguide/pyguide.html#316-naming

Avoid abbreviation. In particular, do not use abbreviations that are ambiguous or unfamiliar to readers outside your project, and do not abbreviate by deleting letters within a word.

ただし、上記のスタイルガイドは唯一絶対のルールではなく、複数あるスタイルガイドの一つに過ぎないということを念頭に置くことをお勧めします。また、所属するチームにより何が良いとされているかは変わります。自分の中で良い書き方の基準を持ちつつ、チームの平均的な書き方で書くことをお勧めいたします。

slow_ptr = head

while fast_ptr and fast_ptr.next:

Choose a reason for hiding this comment

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

スタイルによりますが、fast_ptr is not None と書くべきという派閥があることは知っておいてもいいと思います。PEP8 や、それに準拠する Google Style Guide などですね。

https://peps.python.org/pep-0008/#programming-recommendations

Also, beware of writing if x when you really mean if x is not None – e.g. when testing whether a variable or argument that defaults to None was set to some other value. The other value might have a type (such as a container) that could be false in a boolean context!

fast_ptr = fast_ptr.next.next
slow_ptr = slow_ptr.next

if fast_ptr == slow_ptr:
return True

return False
```

# Step3
```python
class Solution:
def hasCycle(self, head: Optional[ListNode]) -> bool:
fast_ptr = head
slow_ptr = head

while fast_ptr and fast_ptr.next:
fast_ptr = fast_ptr.next.next
slow_ptr = slow_ptr.next

if fast_ptr == slow_ptr:

Choose a reason for hiding this comment

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

ここはどちらかというと identify check なので fast_ptr is slow_ptr の方が正確かなと思います。
https://docs.python.org/3/library/operator.html#mapping-operators-to-functions

return True

return False
```
* 解答時間
- 1回目: 1:10
- 2回目: 1:10
- 3回目: 1:06