Skip to content

82. Remove Duplicates from Sorted List II #5

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

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
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
100 changes: 100 additions & 0 deletions 82. Remove Duplicates from Sorted List II.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# step1
```python
class Solution:
def deleteDuplicates(self, head: Optional[ListNode]) -> Optional[ListNode]:
dummyHead = ListNode(0)
dummyHead.next = head
current = dummyHead
while current.next and current.next.next:
if current.next.val == current.next.next.val:
copy = current.next
while copy.next and copy.val == copy.next.val:
copy = copy.next
current.next = copy.next
else:
current = current.next
Comment on lines +7 to +15
Copy link

Choose a reason for hiding this comment

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

私はこのループはちょっと抵抗感があります。

というのは、これ current の意味付けが、取っておくことが決まったノードの最後ですね。
なので current.next という形ですべて出てきていますね。

これ、自然言語で説明したときに、取っておくもののお尻を「今」と呼んで、今の次のやつの性質を論じないと思うのです。

素直にやると、取っておくもののお尻、と、注目していて重複しているかを確認しているノードの2つに名前をつけて、重複していないことが分かったら、取っておくもののお尻に付け足すというほうが自然でしょう。

また、取っておくもののお尻.next = None にするほうが私は表現としては素直だと思います。

return dummyHead.next
```
#### 思考ログ
- 前回の問題では現在のノードと次のノードを見比べたので、今回は次のノードのその次のノードを見ればいいのではと思ったが、2連で同じノードが続いている場合しか対応できないことに気づいた
- 再帰とか使えば書けるのかなと思っていたら5分経っていたのでaraiさんの解説を視聴
- 解説を見たら理解できたが、なかなか0から書ける様にならないので自然言語で整理してみる
- 1. 現在ノードを基準とし、1つ先のノードとその次のノードが一致しているか確認する
- 2. 一致している場合、それがどこまで続くのかを確認し、同じ値の最後のノードの次のノードを現在ノードの次のノードに設定する。
- 3. 一致していない場合、現在ノードを進める
- 4. 1番目から連続している場合、一番最初にダミーノードを作成しそこを基準に判定する。returnには含めない。
- この思考の順番であっているのか、1番目から連続しているパターンを最初から考えるのか後から考えるのか。個人的には基本的な開放を考えてからエッジパターンを拾うという思考の流れの方が自然な気がする。
- なかなかにわかりにくい文章だが、頭の中で整理できたようで5分で回答が書けるようになった。

# step2
```python
class Solution:
def deleteDuplicates(self, head: Optional[ListNode]) -> Optional[ListNode]:
dummy = ListNode(1)

Choose a reason for hiding this comment

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

最初の値は使わないのでdummy = ListNode(next=head)と書くこともできます。

dummy.next = head
node = dummy
while node.next and node.next.next:
if node.next.val != node.next.next.val:
node = node.next
continue
duplicate = node.next
while duplicate.next and duplicate.val == duplicate.next.val:
duplicate = duplicate.next
node.next = duplicate.next
return dummy.next
```
#### 思考ログ
- step1では新井さんの解説動画の変数名をそのまま使ったが、copyなどの変数名は前回のレビューで指摘された通り汎用的すぎるので別の名前を考える。
- copy → duplicate
Copy link

Choose a reason for hiding this comment

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

copy は、標準ライブラリーの名前と衝突しているのが少し気にかかります。
import copy したときに、copy という名前がぶつかります。

あとで、コードを編集をして copy を使いたくなった人が混乱する(import copy して使おうとしたのにそのスコープで copy が使われているために動かない)可能性があるでしょう。

- 言語化した文章をもう少し推敲してみる
- 1. 現在ノードを基準とし、次とその次のノードの値が一致しているかをみる
- 2. 一致していない場合は現在ノードを次のノードに進める。
- 3. 一致している場合はどこまで一致しているかを調べる。
- 4. 一致しなくなったペアの後ろのノードを現在ノードの次のノードに付け替える
- 5. 一つ目のノードから連続している場合は↑の方法が取れないため、一つ目のノードの前にノードを作成する。
- 6. 一つ目のノードは自分で作ったノードなので、そのノードは飛ばしてreturnする。
- 言語化したらできるようになったことについて、それっぽい話があった
- > 「自分で手作業でできる」「人間にやり方を説明して代わりにやってもらえる」「機械にやり方を説明して代わりにやってもらえる(おおまかに、これがコードが書けること)」の順で難しくなっていくので、より簡単なのができないのだとたぶん書けないのです。https://github.com/atomina1/Arai60_review/pull/3#r1893847805
- > duplicateがある場合とない場合2つに分けられ、また、片方だけ一行で済む様な(考え方として)軽い処理の時に、軽い処理を先に終わらせてしまったほうが、あまり頭を使わない気がします。https://github.com/atomina1/Arai60_review/pull/3#r1893546879
- 確かにとおもったので、ifとelseを逆にする
- Sentinel valueというものがあるそう
- draftだがpepでもこれに関する記載があった(https://peps.python.org/pep-0661/)
- dummyHeadをsentinelという名前にできるかなとも思ったがwikiを見る感じ終了を表すために使われるそうなので用途が違いそう
- https://ja.wikipedia.org/wiki/%E7%95%AA%E5%85%B5#%E7%B5%82%E4%BA%86%E3%82%92%E8%A1%A8%E3%81%99%E3%81%9F%E3%82%81%E3%81%AE%E5%B0%82%E7%94%A8%E3%81%AE%E5%80%A4
- と思ったが、leetCodeの解答例にsentinelが使われているので使っても良さそう
- 命名はgoogleのstyle guideに習った方が良さそうなのでそちらに倣う https://github.com/t0hsumi/leetcode/pull/4/files#r1871480436
- 関数名をいじるとleetCodeで動かなくなるので関数名はそのままでいく
- >で、私の指摘はなんだったかというと、current_value は読んでいる人にとって、ほとんど情報がないです。「現在の値」ですよね。日本語に直すときに「この状態での合計」に直したわけですが、そうだとすると「合計」sum で、少なくとも、current_sum だと、合計である事が分かります。ただ、current も本当は変えたくて、というのも「今」注目しているくらいしか意味がないです。sum_so_far とかなんかあるでしょう。 https://discord.com/channels/1084280443945353267/1225849404037009609/1234206158630289450
- というレスがあった、本コードでもcurrentである必要性を感じないのでnodeとしても良さそう
- dummy_headもheadであるというのはあまり意味を持たないので、dummyだけで良さそうなのでリファクタする
- 1重でもかけるそう
- それぞれの違いは以下
- 1重:重複を発見したら、ノードを見比べて一つ一つノードを削除する。
- 次のループへの引き継ぎのため変数を多く用意する必要があり、メモリ使用量が多くなる。(といっても全ノードに対して変数を用意するわけではないのでO(1))
- 2重:重複を発見したら、重複が存在する全てのノードを一気に削除する。
- ループが二重になるのでtime complexityが増える。
- とおもったが、二重目の方は全て見るわけではないのでO(N)になるらしいい。
- 両方とも計算量は変わらない
- time complexity:O(N)
- space complexity:O(1)

# step3
```python
class Solution:
def deleteDuplicates(self, head: Optional[ListNode]) -> Optional[ListNode]:
dummy = ListNode(1)
Copy link

@olsen-blue olsen-blue Dec 30, 2024

Choose a reason for hiding this comment

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

(レビュー自体初なので何か変な操作をしていたらご一報いただけますと幸いです。)
「1」という値は先頭ノードの値なので、dummyに値を与えるのであれば、被らない値をセットする方が、明示的で良いのかもしれないと個人的には感じました。
私は「0」にしていました。「-1」とかでも良いのかもしれないです。

Copy link

Choose a reason for hiding this comment

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

そもそも指定しないというのもありかなと思います。
この場合は、dummy = ListNode(next=head)とすると、一行分短縮もできます。
https://docs.python.org/3/glossary.html#term-argument

dummy.next = head
node = dummy
while node.next and node.next.next:
if node.next.val != node.next.next.val:
node = node.next
continue
duplicate = node.next
Copy link

Choose a reason for hiding this comment

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

duplicateしているのはnodeではなく値なので、自分なら、duplicate_value = node.next.valにするかなと思います。

while duplicate.next and duplicate.val == duplicate.next.val:
duplicate = duplicate.next
node.next = duplicate.next
return dummy.next
```
#### 思考ログ
- 自然言語で整理する工程を入れたことで、記憶しやすくなったからか一発で3連続回答できた。
- 時間も短くなった。