-
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,225 @@ | ||
| https://leetcode.com/problems/linked-list-cycle/ | ||
|
|
||
| # Step1 | ||
|
|
||
| かかった時間:40min | ||
|
|
||
| ```ruby | ||
| # Definition for singly-linked list. | ||
| # class ListNode | ||
| # attr_accessor :val, :next | ||
| # def initialize(val) | ||
| # @val = val | ||
| # @next = nil | ||
| # end | ||
| # end | ||
|
|
||
| # @param {ListNode} head | ||
| # @return {Boolean} | ||
| def hasCycle(head) | ||
| return false if head.nil? || head.next.nil? | ||
| slow, fast = head, head | ||
|
|
||
| while !fast.nil? && !fast.next.nil? | ||
| slow = slow.next | ||
| fast = fast.next.next | ||
| return true if slow.val == fast.val | ||
| end | ||
| false | ||
| end | ||
| ``` | ||
| 思考ログ: | ||
| - 解説見たことあったので解法はなんとなく頭に入ってるつもり | ||
| - slowとfastのポインタを用意してfastが追い越したらcycle | ||
|
|
||
| 何がわからなかったか | ||
| - `@param {ListNode} head` に入ってくるデータの構造がよくわかってなかった | ||
| - こんな感じのコードを書いて`fast`がnilになったりして困っていた | ||
| ```ruby | ||
| def hasCycle(head) | ||
| slow = head | ||
| fast = head.next | ||
|
|
||
| loop do | ||
| if slow.val == fast.val | ||
| return true | ||
| elsif fast.next.nil? | ||
| return false | ||
| end | ||
| slow = slow.next | ||
| fast = fast.next.next | ||
| end | ||
| end | ||
| ``` | ||
| - >`@param {ListNode} head` に入ってくるデータの構造がよくわかってなかった | ||
| - これはデータ構造が分かってないんじゃなくて、ノードの長さが1だけのときのケースについてケアできてないだけだった | ||
| - 答えを見てから書いたコード | ||
| ```ruby | ||
| def hasCycle(head) | ||
| return false if head.nil? || head.val.nil? | ||
| slow, fast = head, head | ||
|
|
||
| while !fast.nil? && !fast.next.nil? # <- fastがnilで落ちる | ||
|
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. これ、loop do + if return に分解してみて欲しいです。
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. やってみました!仰ってることが肌で分かった気がします。 |
||
| slow = slow.next | ||
| fast = fast.next.next | ||
|
|
||
| return true if slow.val == fast.val | ||
| end | ||
| false | ||
| end | ||
| ``` | ||
| - パスしなかったのでもう一回消して答えを見てから書いた(一番上のコード)。パスしたのでStep1終わり | ||
|
|
||
| 何を考えて解いていたか | ||
| - アルゴリズムの仕組みとしては分かってたもののコードでどう書いたらその通りに動くのかが掴めなくて時間を使った | ||
| - そもそも事前に解説を見たことがあるという半分ズルなので初見だとまず解き方を思いついてなかったと思う | ||
|
|
||
| 正解してから気づいたこと | ||
| - 頭からnilのチェックをするケースを書き始めると混乱するのでやりたいメジャーケース(今回だとwhileの中)から書き始めて、あとから細かいケースについて詰めていくと自分の考えてることと書いてるコードが一致してて書きやすいかもと思った | ||
| - off topic気味だけどleetcodeのエディタにダイレクトで書いてるのはいいのかな(IDE使わないようにしてればOKだと思うが) | ||
|
|
||
| # Step2 | ||
|
|
||
| 参考にした過去ログなど: | ||
| - https://github.com/sendahuang14/leetcode/pull/1/files | ||
| - https://github.com/kagetora0924/leetcode-grind/pull/1/files | ||
|
|
||
| 思考ログ: | ||
| - Step1はwhileの条件が読みにくいかも?改善ポイントってこういうのでいいのかな? | ||
| - わからなかったので他の人のPRを探す | ||
| - https://github.com/sendahuang14/leetcode/pull/1/files | ||
| - HashMapでたどったノードをメモする解法。チラッと考えたけど前に見たことある解法で解くイメージが先行して試してなかった。 | ||
|
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. 講師陣の方によると、setを使った解法の方がオーソドックスというか想定解だと思われますね。 |
||
| - 辞書に書き込むのと配列にpushするのだと辞書に書き込んだ方がO(1)?でアクセスできるので良さそう? | ||
| - そういえば計算量について全然意識できてなかったことに気づく | ||
| - 今のアルゴリズムだとどうなるんだ...? | ||
| - 時間計算量は最大でO(N)? | ||
| - 空間計算量はO(1)でいいのかな | ||
|
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(1)で良いですね。setを使うと最悪計算量がO(N)になるかと思います。(すべてのノードの値をsetに入れることになるため。) |
||
| - 「より自然な」コードだとたどったノードをメモっていくのが直感的な感じがするので辞書にたどったノードを書いてみる | ||
| - と思ったけど辞書のキーがnode.valだったとして、キーに対応する値に何をいれるべきなのか迷った | ||
| - 値にboolean入れる想像をしたけど、結局アクセスする時に返ってくる値がややこしくなるのでやめた | ||
| - のでとりあえず配列に詰めることにした | ||
| - 配列に重複があることをどう判定する? | ||
| - `Array#uniq` は重複を排除した配列を返すので、`visited_nodes.length == visited_nodes.uniq.length`で判定はできるけど冗長な感じがする | ||
| - RubyにSetがあったことを思い出す | ||
| - https://docs.ruby-lang.org/ja/latest/class/Set.html#I_--3C--3C | ||
| - >Set#add? は、集合に要素が追加された場合には self を、変化がなかった場合には nil を返します。 | ||
| - これの返り値で判定できそう | ||
| - とりあえずパスするコードは書けた | ||
| ```ruby | ||
| def hasCycle(head) | ||
| return false if head.nil? || head.next.nil? | ||
|
|
||
| visited_nodes = Set.new | ||
| visited_nodes.add(head.val) | ||
|
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. 私もStep3のコードのように、headを特別扱いしないバージョンの方が好みです。 |
||
| current = head.next | ||
|
|
||
| while !current.nil? do | ||
| has_visited = visited_nodes.add?(current.val).nil? | ||
|
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. これ、val が unique であるという保証あるんでしたっけ? Set は Object 入れられたと思いますので、そちらのほうが素直では。
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. 確認したのですが、勝手にvalがuniqueだと思い込んでました😇 Setにはプリミティブなintとかstringぐらいしか入らないと思い込んでたんですがHashも入るんですね。 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. https://docs.ruby-lang.org/ja/latest/library/set.html
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 true if has_visited | ||
|
|
||
| current = current.next | ||
| end | ||
| false | ||
| end | ||
| ``` | ||
| - セルフツッコミしてみる | ||
| - もうちょっと最初の5行ぐらいをシュッと書けるかも | ||
| - 先にhead.valをaddするんじゃなくて、そのまま全部whileの中に入れられないかな | ||
| - `has_visited = visited_nodes.add?(current.val).nil?`が読みづらい気がする | ||
|
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. includeメソッド使う方が素直な気がします。
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. 確かに!ドキュメント途中までしか見てませんでした 😇 |
||
| - `Set#add?`が要素を追加できなかったらnilを返すことを知ってなきゃシュッと読めない | ||
|
|
||
| # Step3 | ||
|
|
||
| かかった時間:15min | ||
|
|
||
| ```ruby | ||
| def hasCycle(head) | ||
| visited_nodes = Set.new | ||
| current = head | ||
|
|
||
| while !current.nil? do | ||
| has_visited = visited_nodes.add?(current.val).nil? | ||
| return true if has_visited | ||
|
|
||
| current = current.next | ||
| end | ||
| false | ||
| end | ||
| ``` | ||
| 思考ログ: | ||
| - 書きながらどうやったら直感的になるか考えた | ||
| - 何も考えずに最初から「headをcurrentとして、valを詰める→ポインタが示す先に移動する→ノードがあるか確認する」を繰り返すだけで行けるかも | ||
| - 書けた。だいぶ直感的になった気がする。 | ||
| - このぐらいシュッとしてるなら`visited_nodes.add?(current.val).nil?`の部分については許容できるレベルだと思った | ||
| - 変数名については振り返ってなかったけど、見直してもあんまり改善ポイントが浮かばなかった | ||
| - 1発で3回連続パスした!終わり。 | ||
|
|
||
| # Step4 | ||
|
|
||
| レビューを受けて修正する。 | ||
| - >これ、val が unique であるという保証あるんでしたっけ? Set は Object 入れられたと思いますので、そちらのほうが素直では。 | ||
| - https://github.com/canisterism/leetcode/pull/2/files#r1686085757 | ||
| - valはすべてのノードでuniqueでないので、~Hash~ Node自体を詰めないと同じvalが出てくると正しい結果にならない。 | ||
| - 修正するとこうなる | ||
| ```ruby | ||
| def hasCycle(head) | ||
| visited_nodes = Set.new | ||
| current = head | ||
|
|
||
| while !current.nil? do | ||
| has_visited = visited_nodes.add?(current).nil? | ||
| return true if has_visited | ||
|
|
||
| current = current.next | ||
| end | ||
| false | ||
| end | ||
| ``` | ||
| - SetにHashを入れた時の等価の判定ってどうなるんだっけ。多分key:valueで見ると思うけど... | ||
| - https://docs.ruby-lang.org/ja/latest/library/set.html | ||
| - >Set は内部記憶として Hash を使うため、集合要素の等価性は Object#eql? と Object#hash を用いて判断されます。 | ||
| ```ruby | ||
| {'a': 1}.hash | ||
| => 575188353908168896 | ||
| {'a': 2}.hash | ||
| => 1910308263874936072 | ||
| {'a': 1}.eql?({'a': 2}) | ||
| => false | ||
| {'a': 1}.eql?({'a': 1}) | ||
| => true | ||
| ``` | ||
|
|
||
| ```ruby | ||
| set = Set.new | ||
| => #<Set: {}> | ||
| set.add({'a': 1}) | ||
| => #<Set: {{:a=>1}}> | ||
| set.add({'a': 1}) | ||
| => #<Set: {{:a=>1}}> | ||
| set.add({'a': 2}) | ||
| => #<Set: {{:a=>1}, {:a=>2}}> | ||
| ``` | ||
| - 合ってそう。 | ||
| - key:valueでハッシュ値を作ってるぽいけどそれで合ってるのかソースを見に行こうとしたがどうやらCで実装してるぽくて読めなかった | ||
| - https://github.com/ruby/ruby/blob/master/hash.c これかな? | ||
|
|
||
| >loop do + if return に分解してみて欲しいです。 | ||
| https://github.com/canisterism/leetcode/pull/2/files#r1685762007 | ||
| slow, fastのアルゴリズムをloop + if で書き直してみた。 | ||
|
|
||
| ```ruby | ||
| def hasCycle(head) | ||
| slow = head | ||
| fast = head | ||
|
|
||
| loop do | ||
| return false if fast.nil? || fast.next.nil? | ||
|
|
||
| slow = slow.next | ||
| fast = fast.next.next | ||
| return true if slow == fast | ||
| end | ||
| end | ||
| ``` | ||
| - 「次のノードまで見る→進んでもOKだったのでポインタを動かす→一致してるか判定」の流れで書いたら一発でこれが出てきて自分でビックリした。 | ||
| - slow, fastのアルゴリズム(フロイドの循環検出法と言うらしい)苦手だと思ってたけど思考の流れが整理されてないから分からないだけだったのかと思うなど | ||
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.
このコードで一番謎なのは、nil チェックするのと動かすのが対になっていないところでどう考えているのかが知りたいです。動かす前に確認したいし、確認したら動かしたい気持ちになりませんか。
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.
https://discord.com/channels/1084280443945353267/1192728121644945439/1194203372115464272
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.
言われてみれば確かにそうですね。
これを書いた時の気持ちとして、「まず循環してるかの結果が出てたらすぐに判定してループを抜けたい」が先に来てif節を書いてました。
解いてる時にこの順番の発想が浮かびませんでした!ちょっと書き直してみます 👀