-
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?
Changes from all commits
b45e98f
cfc4c47
f6873de
322baa5
de8eadb
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,172 @@ | ||
# 82. Remove duplicate from sorted list II | ||
|
||
## すでに解いた方々(in:レビュー依頼 titleで検索) | ||
- https://github.com/atomina1/Arai60_review/pull/3/files | ||
- https://github.com/t0hsumi/leetcode/pull/4/files | ||
- https://github.com/katataku/leetcode/pull/3/files | ||
- https://github.com/ichika0615/arai60/pull/5/files | ||
- https://github.com/tarinaihitori/leetcode/pull/4/files | ||
|
||
*********************************************************************************************************************************************************************************** | ||
以前「やってることは間違っていない」とコメントをいただいているので、その点は安心しているのですが、step1~3を終えるのに6時間くらいかかってしまいました。 | ||
度々こういうことがありまして、コードの読み書きに不慣れとはいえ時間がかかりすぎで、何か根本的な問題があるような気がしています。 | ||
内訳は | ||
- Step1(2時間くらい):「どの解法がいいかな」とPRを漁るのに時間がかかる。自分の発想に近い解き方をしている人を探していて、工程がStep2と混ざっているかも。 | ||
- Step2 (4時間くらい):レビューをお願いする5人分を確認し、PRを一通り読んで問題に対してどんな解法があるのか一通り理解する。どんな解法があるのか抑える過程にすごく時間がかかるが、これが理解できないとコードがちゃんと読めていない感じがする。 | ||
- Step3(20分くらい):Step2で見た解法のうち、しっくりくるものを思い出しながら再現する。打ってるうちに覚えてくるので、ここはあまり時間はかからない。 | ||
という感じなのですが、改善すべき意識や工程などありますか…?それとも初心者はこんなもので、やってるうちに早くなるものなんでしょうか? | ||
*********************************************************************************************************************************************************************************** | ||
|
||
## Step 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 commentThe 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 commentThe reason will be displayed to describe this comment to others. Learn more. 恥ずかしながらこのご指摘をいただいてはじめて、LeetCode上で標準出力が確認できることに気づきました。。。 |
||
-> 一度自分で解いた解法じゃないと他の人のコードを全然読めていなくて、そもそもコードを読んだり頭の中で挙動を再現する力が弱そう。現状Step1の時点では「誰かの回答を写経して、気になるところを変えながら挙動を理解していく」とかなら時間がかからなそうなので、次は試してみる。 | ||
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. 個人的には1問1時間位で考えています。あまり長いと退屈に感じるでしょう。この練習方法は学習曲線の傾きを急にする方法で、一番の敵は、難しくて辛いというよりは退屈なことです。つまり、報酬を感じないことです。 |
||
- Inplaceとnot-inplaceというらしい: https://github.com/cheeseNA/leetcode/pull/9/files | ||
|
||
### 当初の発想で書いたコード | ||
```Python | ||
class Solution: | ||
def deleteDuplicates(self, head: Optional[ListNode]) -> Optional[ListNode]: | ||
dummy = ListNode(None, head) | ||
node = dummy | ||
|
||
value_duplicated = set() | ||
|
||
while node.next: | ||
if node.val == node.next.val: | ||
value_duplicated.add(node.val) | ||
node = node.next | ||
|
||
node = dummy | ||
|
||
while node: | ||
while node.next and node.next.val in value_duplicated: | ||
node.next = node.next.next | ||
node = node.next | ||
|
||
return dummy.next | ||
``` | ||
|
||
## Step 2 | ||
### 学んだこと | ||
- 例によってsortされてるのでsetとして重複変数を保存する必要はなかった。while文1つにまとめれそう https://discord.com/channels/1084280443945353267/1195700948786491403/1196701558382018590 。 | ||
- 長すぎる条件が気になっていたが、これをまとめるアイデアがあった: https://github.com/rinost081/LeetCode/pull/6#discussion_r1744911468 | ||
- 大きく分けて解き方は3つっぽい。再帰で解く、繋いでから切る、2つListNodeを用意して重複のないものだけを繋いでいく。 | ||
- https://github.com/tarinaihitori/leetcode/pull/4/files#r1807830600 を見ると、.next.nextを用いたやり方が自分の最初の発想と連続していて、素直で理解しやすいと感じる。重複の判定を別の関数でやるのもスッキリしているのでこれを目指す。 | ||
- whileとif breakはwhile notに書き換えれるらしい: https://discord.com/channels/1084280443945353267/1227073733844406343/1228598526712483902 | ||
- If elseを見ると読み慣れている人はこんなふうに考えるのか: https://github.com/atomina1/Arai60_review/pull/3/files#r1893541458 | ||
- 命名としてdummyではなくsentinel, valじゃなくてvalueの方が良さそう:https://github.com/katataku/leetcode/pull/3/files | ||
|
||
### 繋いでから切るコード | ||
```Python | ||
class Solution: | ||
def deleteDuplicates(self, head: Optional[ListNode]) -> Optional[ListNode]: | ||
sentinel = ListNode(None, head) | ||
node = sentinel | ||
|
||
while node: | ||
is_duplicated = False | ||
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. フラグが使われたコードは、読むにあたりやや認知負荷が高くなるように思います。無理なく避けられるのであれば、避けたほうが良いと思います。以下のようなコードはいかがでしょうか?
一部コードが重複してしまっていますが、許容範囲だと思います。 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. 当初はご提示いただいたものと近い方向で考えていたのですが、条件分が長くなったり重複するのが嫌で他の方のコードを真似して書いてみました。フラグを使うことの認知負荷自体考えたこともなかったので、今後他の方のコードを読む際に意識してみます。 |
||
while node.next and node.next.next and node.next.val == node.next.next.val: | ||
is_duplicated = True | ||
node.next = node.next.next | ||
if is_duplicated: | ||
node.next = node.next.next | ||
else: | ||
node = node.next | ||
|
||
return sentinel.next | ||
``` | ||
### 重複のないものだけを繋いでいくコード | ||
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. 質問なんですが、この解法って「切断しておいて使うものだけ繋ぐ」解法とは違うものと考えていいんでしょうか? 書かれている解法では 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. 繋ぐ、繋がないみたいなのの自分のイメージは以下の通りとなります 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. 遅くなってすいません。実はこの辺何度読んでも理解できていないのですが、もう少し考えてみます…。
自分は 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. あー、ループを仕事の引き継ぎだと思いましょう。 ループを引き継ぐ瞬間に、前日のシフトが今日のシフトに申し送りをします。
ところで、ここでもう一つ、引き継ぎの瞬間に「unique.next == scan」を約束しているか、という問題があります。 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. お2人のコメントを交互に読んで、やっと理解できたと思います。
1つ目のifのところだけunique.next = scanが担保されてないところですかね...?日をおいてもう一度考えてみます。 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. 理解した気がします。僕が各変数に割り当てられている意味を十分に意識していなかったですね。
で、明示的に重複のないノードまでの変数(unique)を置いているかが違いますね。 一方で、
とすれば書けますね。 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. そうですね。 一応、もう一つの選択肢として、引き継ぎの瞬間に「unique.next is None」か「unique.next == scan」は不明。次の日の人が片付けろ。というのもあります。 理解して意識的にどちらでも対応するとしていればいいのですが、分かっていない場合は、初日とそれ以外の日でやっていることが違うのでこの現象が起きて、そして、ループが終わった後に後始末をしなくてはいけなくなったりします。 二分探索で混乱している人も、これと基本的に同じところで混乱しているように見えるので、二分探索の理解にもこれは役に立つかもしれませんね。 |
||
```Python | ||
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 | ||
Comment on lines
+98
to
+99
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. 以下の方がuniqueに追加して進めている感じが強いと思いましたが、好みだと思います。 unique.next = scan
unique = unique.next |
||
# scan.nextがNoneの時は終了 | ||
else: | ||
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. この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 commentThe 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 commentThe 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 commentThe reason will be displayed to describe this comment to others. Learn more. せっかく教えていただいたので後で見やすい様に追記
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
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 |
||
print(scan.next) | ||
unique.next = scan | ||
break | ||
|
||
scan = scan.next | ||
|
||
return dummy.next | ||
``` | ||
### 再帰を使ったコード | ||
回答を見ていると再帰を使っている人も多いので、練習のため1度再帰で通してみる。 | ||
|
||
```Python | ||
class Solution: | ||
def deleteDuplicates(self, head: Optional[ListNode]) -> Optional[ListNode]: | ||
if head is None or head.next is None: | ||
return head | ||
|
||
if head.val != head.next.val: | ||
head.next = self.deleteDuplicates(head.next) | ||
return head | ||
|
||
while head.next is not None and head.val == head.next.val: | ||
head = head.next | ||
return self.deleteDuplicates(head.next) | ||
``` | ||
|
||
|
||
|
||
## Step 3 | ||
- ListNodeの問題、Nodeを1個ずつ処理していくイメージがあったので、while node:~ node = node.nextでかけて欲しい気持ちがあるが、良い書き方が思い浮かばなかった。 | ||
- 3:54, 3:21, 3:08 | ||
```Python | ||
class Solution: | ||
def deleteDuplicates(self, head: Optional[ListNode]) -> Optional[ListNode]: | ||
sentinel = ListNode(None, head) | ||
node = sentinel | ||
|
||
while node: | ||
is_duplicate = False | ||
while node.next and node.next.next and node.next.val == node.next.next.val: | ||
node.next = node.next.next | ||
is_duplicate = True | ||
if is_duplicate: | ||
node.next = node.next.next | ||
else: | ||
node = node.next | ||
|
||
return sentinel.next | ||
``` | ||
### Step4? | ||
しっくり来てなかったので日をおいて解き直し | ||
```Python | ||
class Solution: | ||
def deleteDuplicates(self, head: Optional[ListNode]) -> Optional[ListNode]: | ||
sentinel = ListNode(0, head) | ||
ptr = sentinel | ||
|
||
while ptr: | ||
scan = ptr | ||
if scan.next and scan.next.next and scan.next.val == scan.next.next.val: | ||
while ( | ||
scan.next and scan.next.next and scan.next.val == scan.next.next.val | ||
): | ||
scan.next = scan.next.next | ||
scan.next = scan.next.next | ||
else: | ||
ptr.next = scan.next | ||
ptr = ptr.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.
6時間は長めな気はしますが、はじめ3時間位だったのが30問くらいで1時間を切るようになった人もいると聞いています。