141. Linked List Cycle - #1
Conversation
|
pythonにあまり明るくないので、他の範囲でコメント致します。 |
| visited_nodes = set() | ||
| current_node = head | ||
|
|
||
| while current_node != None: |
There was a problem hiding this comment.
Noneとの比較はis や is notを使うのが良さそうです。
Comparisons to singletons like None should always be done with is or is not, never the equality operators.
https://peps.python.org/pep-0008/#programming-recommendations
これからLeetCodeを解いていく中でデザインに関して疑問に感じた箇所はpep8を参照するのがいいと思います。
There was a problem hiding this comment.
ありがとうございます!!
確かに None などの singleton との比較で is か is not を使わないのは直感的でないですね。
step 4として修正してみました!
Add step4
| slow = slow.next | ||
| fast = fast.next.next | ||
|
|
||
| if (slow == fast): |
There was a problem hiding this comment.
ありがとうございます!!
ちなみに pep8 には記載がなかったので、一旦括弧なしで統一してみることにします。(任意のものをあえて毎回書くのは冗長だなと思った次第です)
step4として修正してみました!
Add step4
|
だいたい皆さんと同じ感想が浮かびましたが、概ね読みやすかったです。 |
| 初回トライでは、走査した ListNode を dict に格納して ({ListNode: Bool} の形を想定)、 head.Next() を loop で回しながら dict と照合させるのがいいと思って進めていたが、そもそも Python でどうやって dict を作るのかや loop を回したらいいかがパッと出てこなく、調べていたら 5 分経ってしまっていた。本協会に入るまで Python に触ったことはほぼなく、入会後に LearnPython を数日でサッとこなした程度の習熟度だったのが要因と考えている。 | ||
|
|
||
| 2 回目のトライで 5 分以内に正解にできた。LeetCode の[こちらの回答](https://leetcode.com/problems/linked-list-cycle/solutions/3999014/99-68-two-pointer-hash-table/)を参考にした。Code Two-Pointer と Code Hash Table の 2 パターンがあったが、Code Two-Pointer は時間計算量こそ小さいが直感的でないと感じた (Floyd's Tortoise and Hare Algorithm に則ったものらしい。([記事](https://medium.com/@anudeepballa7/floyds-tortoise-and-hare-algorithm-6d439cdefde5)) ) ので、Code Hash Table の方を選択した。今回の目的は既出の ListNode と照らし合わせることが目的だったので元々考えていた dict は冗長だったことに気づけた。 | ||
| また、解答例はなぜ list() ではなく set()なのかと思ったが、前者は ordered、後者は unordered な collection で前者の方がオーバーヘッドがあるためと理解した。現に、LeetCode で list()に書き換えて提出してみたら、時間計算量に大きな違いがあり、興味深かった。(set()が Runtime: 36ms に対して、list()が Runtime: 843ms) |
There was a problem hiding this comment.
時間計算量と実行時間を混同しているように見受けられました。時間計算量は入力データのサイズに対し、計算ステップ数がどのように変化するかを表す式です。実行時間は実行に掛かった実際の時間です。実行時間は時間計算量とデータサイズからある程度推測できます。詳しくは以下のコメントをご覧ください。
https://discord.com/channels/1084280443945353267/1196498607977799853/1270740093220687903
There was a problem hiding this comment.
ご指摘ありがとうございます!
はい、おっしゃる通り、混同していました 🙏
今回のmemoの内容だと、「時間計算量に大きな違いがあり」 -> 、「実行時間に大きな違いがあり」 とすべきでしたね。
時間計算量的には、以下が言えるということですね
- list() でも set() でもO(n)になる
- 今回の hare and tortoise algorithm と set() を使った解答はともにO(n)と言える (前者は速いポインタが遅いポインタに追いつくまでにリスト全体を走査する可能性があり、後者も同様に一致する node を見つけるまでにリスト全体を走査する可能性がある)
There was a problem hiding this comment.
list() でも set() でもO(n)になる
要素の検索にかかる時間計算量についてであれば、 list は先頭から一つずつ要素を探すため O(n) になります。一方、 set() は CPython であればハッシュテーブルが使われるため、 O(1) になります。
There was a problem hiding this comment.
setのunorderedのところでもう少し、hasableだから探索が短いstepで済むみたいな言及が欲しいかもしれないです。また、平均計算量ではなくて、最悪計算量で思考されてる印象を受けました。
Runtimeは手元で計測する方が良いかもしれません。
brood0783/arai60#2 (comment)
There was a problem hiding this comment.
ありがとうございます!
setのunorderedのところでもう少し、hasableだから探索が短いstepで済むみたいな言及が欲しいかもしれないです
なるほど、盲点でした。cpythonでもhash化して探索している様子が確認できますね。(9個の連続したエントリをチェックし、そのあとはランダムにチェック)
https://github.com/python/cpython/blob/481d5b54556e97fed4cf1f48a2ccbc7b4f7aaa42/Objects/setobject.c#L79-L121
また、平均計算量ではなくて、最悪計算量で思考されてる印象を受けました。
こちらも意識していなかったです。
平均計算量で見ると、listもsetも要素の追加はO(1)になり、検索だとlistがO(n)、setがO(1)になりますね。
一方で最悪検索量で見てみても、要素の追加はlistでもsetでもO(n)になり(配列のリサイズ・コピーが必要になる場合。ただしsetは稀)、検索だとlistもsetもO(n)になりますね (setはハッシュ値の衝突が発生した場合。こちらも稀)。
今後は基本は平均計算量で思考するようにし、最悪計算量も必要に応じて考えるようにします。
Runtimeは手元で計測する方が良いかもしれません。
確かにleetcode上だとだいぶブレますもんね。
手元でテストデータを100個作って計測してみました!
やはり 実行時間は list() > set() > hare and tortoise になるようでした
There was a problem hiding this comment.
listもsetも時間計算量がO(n)というところが気になってコメントしたかったのですが、ご自身で解決されたようで良かったです。
| ### Step 3 | ||
|
|
||
| set()を使う方法で実装。3 回実装してみて大きく内容は変わらなかったが、3 回目で問題文に出てくる語彙を使う方が読み手側にとってより直感的かなと思い、visited_nodes から reached_nodes という名前に変えてみた。 |
There was a problem hiding this comment.
visitedで良い気がします。reachだと到達なので、そこで終わるニュアンスが強い気がします(「範囲」という意味で使っている場合は、走査の範囲のニュアンスがあり、これもカタカナ語に近い違和感があります。そもそも一意に意味が確定しずらい)。visited_flagとかの方が聞いたことが多いので、あくまで一つの提案です。
There was a problem hiding this comment.
ありがとうございます!
確かにcambridge dictionaryを見てみても、reach の意味としてはto arrive at a place, especially after spending a long time or a lot of effort travelingなので、やはり到達と言う意味で捉える方が良さそうですね。
https://dictionary.cambridge.org/us/dictionary/english/reach
There was a problem hiding this comment.
問題を解いた反省としてのMEMOと、次に繋げる改善としてのMEMOが両方MEMOになってるので、分けて書くか、何らかの工夫をするともっと読みやすくなると思います。
現状「step4から改善なんだな〜」と、空気を読めばわかるのですが...
There was a problem hiding this comment.
現状「step4から改善なんだな〜」と、空気を読めばわかるのですが...
ありがとうございます!
Step 4の説明のところに「レビューをもとに改善」と記載しているのですが、わかりづらかったですね。
https://github.com/kazizi55/coding-challenges/pull/1/files#diff-0ac89d0fb94529b2b9ec468c4157cfdfc6e0406d959283f2e8307878c7a1a213R12-R13
フィードバックであることを明示してみました!今後もその方針でやってみます
9d1c628



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