-
Notifications
You must be signed in to change notification settings - Fork 0
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
Create remove_duplicate_from_sorted_list_II #5
base: main
Are you sure you want to change the base?
Conversation
- 皆さんのコードを眺めてみると、ListNode(0)の次にheadを置いている。言われてみれば当たり前だが、これはサッパリ思いつかなかった。 | ||
- Optional[int]を使うとダミー用の番号は不要でNoneで動くらしい:https://discord.com/channels/1084280443945353267/1226508154833993788/1246022270984392724 | ||
- 答えを見たが一度読んだだけではしっくりこず、元の発想通り重複がみられた変数をset()に放り込む方法で書いてみた。納得はできたが、「なぜこれだと動かないのか」「なぜここを変えると動くようになるのか」を納得するのに時間がかかりすぎている感がある(今回ならstep1に2時間とかかかってしまった…)。 | ||
-> 一度自分で解いた解法じゃないと他の人のコードを全然読めていなくて、そもそもコードを読んだり頭の中で挙動を再現する力が弱そう。現状Step1の時点では「誰かの回答を写経して、気になるところを変えながら挙動を理解していく」とかなら時間がかからなそうなので、次は試してみる。 |
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.
個人的には1問1時間位で考えています。あまり長いと退屈に感じるでしょう。この練習方法は学習曲線の傾きを急にする方法で、一番の敵は、難しくて辛いというよりは退屈なことです。つまり、報酬を感じないことです。
step 3 で「はじめはかかっていたものが、さくっと書けるようになること」自体を報酬に感じるのが理想なのですが。
- Step2 (4時間くらい):レビューをお願いする5人分を確認し、PRを一通り読んで問題に対してどんな解法があるのか一通り理解する。どんな解法があるのか抑える過程にすごく時間がかかるが、これが理解できないとコードがちゃんと読めていない感じがする。 | ||
- Step3(20分くらい):Step2で見た解法のうち、しっくりくるものを思い出しながら再現する。打ってるうちに覚えてくるので、ここはあまり時間はかからない。 | ||
という感じなのですが、改善すべき意識や工程などありますか…?それとも初心者はこんなもので、やってるうちに早くなるものなんでしょうか? | ||
*********************************************************************************************************************************************************************************** |
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.
6時間は長めな気はしますが、はじめ3時間位だったのが30問くらいで1時間を切るようになった人もいると聞いています。
- 1問前の.nextを一つ先まで見ればいけそうだなと思い、node_next = node.nextという変数を導入してトライしてみるが、初手で1, 1と続いたケースが通らない。headを動かしたり重複がみられた変数をset()に放り込むなどやってみたが、こんがらがってきたところでギブアップ。 | ||
- 皆さんのコードを眺めてみると、ListNode(0)の次にheadを置いている。言われてみれば当たり前だが、これはサッパリ思いつかなかった。 | ||
- Optional[int]を使うとダミー用の番号は不要でNoneで動くらしい:https://discord.com/channels/1084280443945353267/1226508154833993788/1246022270984392724 | ||
- 答えを見たが一度読んだだけではしっくりこず、元の発想通り重複がみられた変数をset()に放り込む方法で書いてみた。納得はできたが、「なぜこれだと動かないのか」「なぜここを変えると動くようになるのか」を納得するのに時間がかかりすぎている感がある(今回ならstep1に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.
他人のコードが読めないときには print 文を大量に挟んで実行してみるのがお勧めです。
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.
恥ずかしながらこのご指摘をいただいてはじめて、LeetCode上で標準出力が確認できることに気づきました。。。
おかげ様で全てのステップが格段に楽になると思われます。ご丁寧に指摘いただきありがとうございました。
unique.next = scan | ||
unique = scan | ||
# scan.nextがNoneの時は終了 | ||
else: |
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.
この3つの条件、
if not scan.next:
unique.next = scan
break
を一番上に持ってくると、次の条件は、scan.next を前提にできるので scan.val == scan.next.val で if-else にできますね。
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.
あるいは、そのまま break せずに scan = scan.next とすれば、while の条件をそのまま抜けてくれます。
個人的には「重複のないものだけを繋いでいくコード」が一番人が手でやるんだったら考えそうな方法に思えるのでこれが理解できたのはよいと思います。(こういうところを報酬にできるといいです。ゲームの隠し要素を発見みたいな感覚で自分で自分を褒められるかどうかです。)
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.
せっかく教えていただいたので後で見やすい様に追記
if not scan.next:を一番上に持ってくる
class Solution:
def deleteDuplicates(self, head: Optional[ListNode]) -> Optional[ListNode]:
dummy = ListNode(None, head)
unique = dummy
scan = head
while scan:
# 値が重複するときはscanを走査してスキップ
if not scan.next:
unique.next = scan
break
if scan.val == scan.next.val:
while scan.next and scan.val == scan.next.val:
scan.next = scan.next.next
unique.next = scan.next
# 値が重複しないときはuniqueに追加
else:
unique.next = scan
unique = scan
scan = scan.next
return dummy.next
break せずに scan = scan.next とすれば、while の条件をそのまま抜けてくれます
ここまでのif elseですでにscan.nextが存在するケースは抑えてるのでbreakしなくても良い。
class Solution:
def deleteDuplicates(self, head: Optional[ListNode]) -> Optional[ListNode]:
dummy = ListNode(None, head)
unique = dummy
scan = head
while scan:
# 値が重複するときはscanを走査してスキップ
if scan.next and scan.val == scan.next.val:
while scan.next and scan.val == scan.next.val:
scan.next = scan.next.next
unique.next = scan.next
# 値が重複しないときはuniqueに追加
elif scan.next and scan.val != scan.next.val:
unique.next = scan
unique = scan
else:
unique.next = scan
scan = scan.next
return dummy.next
unique.next = scan | ||
unique = scan |
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.
以下の方がuniqueに追加して進めている感じが強いと思いましたが、好みだと思います。
unique.next = scan
unique = unique.next
|
||
return sentinel.next | ||
``` | ||
### 重複のないものだけを繋いでいくコード |
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.
質問なんですが、この解法って「切断しておいて使うものだけ繋ぐ」解法とは違うものと考えていいんでしょうか?
書かれている解法ではdummy = ListNode(None, head)
でdummy.nextがheadを指す状態でスタートして、whileループ内で、ノードの繋ぎ変えをしているので、「繋いでから切るコード」と何が違うのかよくわからなかったです。
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.
遅くなってすいません。実はこの辺何度読んでも理解できていないのですが、もう少し考えてみます…。
自分は
切断しておいて繋ぐ: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.
あー、ループを仕事の引き継ぎだと思いましょう。
「直径 1 m 金属の輪っか」に 2 m の鎖が生えていてその先に「南京錠」がついているとしましょう。LinkedList とは、これを一直線に並べて、南京錠を隣の輪っかにつなげていったものです。最後の南京錠は何にもつながっていません。
という状況です。
ループを引き継ぐ瞬間に、前日のシフトが今日のシフトに申し送りをします。
- 「dummy からはじまって unique で終わる」まで繋がっている一連のやつは、重複が除かれた鎖。
- scan から始まる部分は、まだ処理されてない鎖。
ところで、ここでもう一つ、引き継ぎの瞬間に「unique.next == scan」を約束しているか、という問題があります。
どちらでもできるが決めておかないと後の人がどうするか分からなくなります。
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人のコメントを交互に読んで、やっと理解できたと思います。
引き継ぎの瞬間に「unique.next == scan」を約束しているか、という問題があります。
どちらでもできるが決めておかないと後の人がどうするか分からなくなります。
1つ目のifのところだけunique.next = scanが担保されてないところですかね...?日をおいてもう一度考えてみます。
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.
理解した気がします。僕が各変数に割り当てられている意味を十分に意識していなかったですね。
- 繋いでから切るコード
- 「sentinelからnodeまで」が処理済み
- node.next以降が未処理
- 重複のないものだけを繋いでいくコード
-
「dummy からはじまって unique で終わる」まで繋がっている一連のやつは、重複が除かれた鎖。
-
scan から始まる部分は、まだ処理されてない鎖。
-
引き継ぎの瞬間に「unique.next == scan」を約束
-
で、明示的に重複のないノードまでの変数(unique)を置いているかが違いますね。
一方で、
- 切断しておいて繋ぐ場合、
- 「dummy からはじまって unique で終わる」まで繋がっている一連のやつは、重複が除かれた鎖。
- scan から始まる部分は、まだ処理されてない鎖。
- 引き継ぎの瞬間に「unique.next is None」を約束
とすれば書けますね。
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.
そうですね。
一応、もう一つの選択肢として、引き継ぎの瞬間に「unique.next is None」か「unique.next == scan」は不明。次の日の人が片付けろ。というのもあります。
理解して意識的にどちらでも対応するとしていればいいのですが、分かっていない場合は、初日とそれ以外の日でやっていることが違うのでこの現象が起きて、そして、ループが終わった後に後始末をしなくてはいけなくなったりします。
二分探索で混乱している人も、これと基本的に同じところで混乱しているように見えるので、二分探索の理解にもこれは役に立つかもしれませんね。
node = sentinel | ||
|
||
while node: | ||
is_duplicated = False |
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 node.next and node.next.next and node.next.val == node.next.next.val:
while node.next.next and node.next.val == node.next.next.val:
node.next = node.next.next
node.next = node.next.next
else:
node = node.next
一部コードが重複してしまっていますが、許容範囲だと思います。
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://leetcode.com/problems/add-two-numbers/description/