-
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
Add 82. Remove Duplicates from Sorted List II.md #4
base: main
Are you sure you want to change the base?
Changes from 1 commit
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,241 @@ | ||
# step 1 | ||
[83. Remove Duplicates from Sorted List](https://leetcode.com/problems/remove-duplicates-from-sorted-list/description/)と | ||
やっていることはほぼ同じ。 | ||
今注目しているノードを元に、次に進むべきノードを適切に取ってくる。 | ||
```python3 | ||
class Solution: | ||
def deleteDuplicates(self, head: Optional[ListNode]) -> Optional[ListNode]: | ||
def hasSameValueAsNextNode(node): | ||
return node is not None and node.next is not None \ | ||
and node.val == node.next.val | ||
def skipDuplicates(node): | ||
if node is None: | ||
return None | ||
|
||
while hasSameValueAsNextNode(node): | ||
node = node.next | ||
if hasSameValueAsNextNode(node.next): | ||
return skipDuplicates(node.next) | ||
return node.next | ||
|
||
dummy_head = ListNode(-1, head) | ||
node = dummy_head | ||
while node is not None: | ||
next_node = node.next | ||
if hasSameValueAsNextNode(next_node): | ||
next_node = skipDuplicates(next_node) | ||
node.next = next_node | ||
node = node.next | ||
return dummy_head.next | ||
``` | ||
|
||
初めは以下のような、dummy_headのところを`previous`とし、 | ||
次に繋げるべきノードを`node`とするような解き方をしていた。 | ||
が、これでは末尾にduplicatesがあると`previous.next`がNoneとなり、エラーで動かない。 | ||
この解法でも、適切な位置でNoneチェックを入れれば動くが、そもそも | ||
Singly-linked listであれば、previousの値を気にするような解き方よりは、 | ||
今注目しているノードとその先のノードの関係を調べるような解き方の方が素直だと感じて修正した。 | ||
入力が特定のデータ構造を持っているのなら、解法もそのデータ構造に合わせておきたい。 | ||
```python3 | ||
class Solution: | ||
def deleteDuplicates(self, head: Optional[ListNode]) -> Optional[ListNode]: | ||
def isSameAsNext(node): | ||
return node is not None and node.next is not None \ | ||
and node.val == node.next.val | ||
def skipDuplicates(node): | ||
if node is None: | ||
return None | ||
while node.next is not None and node.val == node.next.val: | ||
node = node.next | ||
if isSameAsNext(node.next): | ||
return skipDuplicates(node.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. 同じ値を持つ一連のノードを取り除いたあと、その直後の同じ値を持つ一連のノードを取り除くために再起関数を使うという点が、不必要に複雑に感じました。 |
||
return node.next | ||
|
||
dummy_head = ListNode(-1, head) | ||
previous = dummy_head | ||
node = head | ||
while node is not None: | ||
if isSameAsNext(node): | ||
previous.next = skipDuplicates(node) | ||
previous = previous.next | ||
node = previous.next | ||
return dummy_head.next | ||
``` | ||
|
||
修正可能箇所 | ||
- `skipDuplicates()`が再帰関数となっている。 | ||
今回は、入力ノード数がデフォルトのrecursion limit(1000)以下なので問題ないが、 | ||
pythonのpythonの末尾再帰最適化がどうなっているのかよくわかっていないので、 | ||
この書き方でノード数が増えたらどうなるのか聞かれたら答えられない。 | ||
- `hasSameValueAsNextNode(node)`という関数名。 | ||
やりたいこと自体は伝わると思うが、inner functionにしては少し長い気もする。 | ||
- magic number:`dummy_head = ListNode(-1, head)`としているが、どうせsentinelとして用いるだけなので、値はいらない。 | ||
- `dummy_head = ListNode(-1, head)`:解法が切断しておいて、duplicateでない値であったらくっつけるやり方をしているのに、dummy_head.nextがheadになっているのが気持ち悪い。 | ||
|
||
nをノード数として | ||
- time complexity: O(n) | ||
- space complexity: O(n) (Auxiliary space: O(1)) | ||
|
||
# step 2 | ||
参考 | ||
- https://github.com/konnysh/arai60/pull/4 | ||
切断しておいて確定したら繋ぐタイプの解法 | ||
- https://github.com/ichika0615/arai60/pull/5/files | ||
切断しておいて確定したら繋ぐタイプの解法 | ||
- https://github.com/tarinaihitori/leetcode/pull/4 | ||
繋いでおいて確定したら切るタイプの解法、その他set()を使ったものなど | ||
- https://discord.com/channels/1084280443945353267/1227073733844406343/1228673329284513843 | ||
切断しておいて確定したら繋ぐ方法と、繋いでおいて確定したら切る方法があるという話。切断しておいて繋ぐ方が自然に感じた。 | ||
- https://neopythonic.blogspot.com/2009/04/tail-recursion-elimination.html | ||
pythonはtail recursive eliminationをサポートしていないみたい。 | ||
実際、手元でtail recursiveなfactorial関数を作り`sys.getrecursionlimit()`(1000)を超える値で動かしてみたがRecursionErrorとなった。 | ||
今回の実装で再帰を使うメリットは、入力されたリストにcycleがあった場合、Recursion Errorとなる。 | ||
デメリットとしては、入力サイズが大きい場合、cycleのない入力であってもRecursion Errorとなる。 | ||
|
||
(切断しておいて繋ぐ)二重ループでの解法 | ||
```python3 | ||
class Solution: | ||
def deleteDuplicates(self, head: Optional[ListNode]) -> Optional[ListNode]: | ||
dummy_head = ListNode() | ||
tail = dummy_head | ||
node = head | ||
while node is not None: | ||
if node.next is None or node.val != node.next.val: | ||
tail.next = node | ||
tail = tail.next | ||
node = node.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. コメントありがとうございます。
みたいな認識でした。 |
||
continue | ||
duplicated_value = node.val | ||
while node.next is not None and node.next.val == duplicated_value: | ||
node = node.next | ||
node = node.next | ||
tail.next = None | ||
return dummy_head.next | ||
``` | ||
(切断しておいて繋ぐ)一重ループでの解法 | ||
```python3 | ||
class Solution: | ||
def deleteDuplicates(self, head: Optional[ListNode]) -> Optional[ListNode]: | ||
dummy_head = ListNode() | ||
tail = dummy_head | ||
node = head | ||
has_duplicate = False | ||
while node is not None: | ||
if node.next is not None and node.val == node.next.val: | ||
has_duplicate = True | ||
node = node.next | ||
continue | ||
|
||
if has_duplicate: | ||
has_duplicate = False | ||
else: | ||
tail.next = node | ||
tail = tail.next | ||
node = node.next | ||
tail.next = None | ||
return dummy_head.next | ||
``` | ||
|
||
(繋いでおいて切断する)二重ループ解法 | ||
```python3 | ||
class Solution: | ||
def deleteDuplicates(self, head: Optional[ListNode]) -> Optional[ListNode]: | ||
dummy_head = ListNode(next=head) | ||
node = dummy_head | ||
while node is not None: | ||
Comment on lines
+144
to
+145
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. nodeをNoneチェックする必要があるか疑問に感じました。 繋いでから切断、切断してから繋ぐみたいなのも、自分自身ちゃんとは分かっていないので、もしかしたら変な指摘かもしれません🙇♂️ 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.
こちらですが、言い換えただけにはなりますが、自分にとって腑に落ちたイメージを話します。
次に相違点ですが、「与えられたsingly-linked listをheadから順に見ていく」ループの中で、保持している値に違いがあります。
ですので、例えば
どちらの解法も考えやすい部分と、考えにくい部分があると思います。
こちらはおそらく型に誤解があるのではないかと感じました。 こちらが参考になれば幸いです。コメントありがとうございます。 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.
これ、私が適当に様子を表現しただけで、特に専門用語ではありませんので、それほどこれ自体を真面目に考える必要はないです。 ただ、これ、ループごとに仕事の引き継ぎをしていると思う(物理的にこれを繋ぎ変える仕事をしていると考える、下のリンク参照)と、「こういう状態にして引き継ぐからね」という約束をどうするかはとても大切なはずです。 ichika0615/arai60#3 (comment) 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. 丁寧な説明ありがとうございます!繋いでから切断、切断してから繋ぐのイメージがわかりました
自分の書き方が悪くて意図を伝えられてませんでした。意図としてはnodeの初期値がListNodeなら、ループが進んでも同様に、nodeをOptionalの付かないListNodeの型にしておける書き方がある、ということを伝えたかったです。自分が書いた1重whileのコードと動作が同じなのに、こちらだと2重whileのコードになっていたので、nodeのNoneチェックするwhileを減らせると思いコメントしました。 |
||
has_duplicate = 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. has_duplicate フラグで処理を切り替える部分が不必要に複雑に感じました。 step3 の最初のソースコードのほうがシンプルだと感じました。 |
||
while node.next is not None and node.next.next is not None \ | ||
and node.next.val == node.next.next.val: | ||
has_duplicate = True | ||
node.next = node.next.next | ||
if has_duplicate: | ||
node.next = node.next.next | ||
else: | ||
node = node.next | ||
return dummy_head.next | ||
``` | ||
|
||
(繋いでおいて切断する)一重ループ解法 | ||
```python3 | ||
class Solution: | ||
def deleteDuplicates(self, head: Optional[ListNode]) -> Optional[ListNode]: | ||
dummy_head = ListNode(next=head) | ||
node = dummy_head | ||
has_duplicate = False | ||
while node is not None: | ||
if node.next is not None and node.next.next is not None \ | ||
and node.next.val == node.next.next.val: | ||
has_duplicate = True | ||
node.next = node.next.next | ||
continue | ||
|
||
if has_duplicate: | ||
has_duplicate = False | ||
node.next = node.next.next | ||
else: | ||
node = node.next | ||
return dummy_head.next | ||
``` | ||
|
||
# step 3 | ||
```python3 | ||
class Solution: | ||
def deleteDuplicates(self, head: Optional[ListNode]) -> Optional[ListNode]: | ||
dummy_head = ListNode() | ||
tail = dummy_head | ||
node = head | ||
while node is not None: | ||
if node.next is None or node.val != node.next.val: | ||
tail.next = node | ||
tail = tail.next | ||
node = node.next | ||
continue | ||
|
||
duplicated_value = node.val | ||
while node.next is not None and node.next.val == duplicated_value: | ||
node = node.next | ||
node = node.next | ||
tail.next = None | ||
return dummy_head.next | ||
``` | ||
|
||
```python3 | ||
class Solution: | ||
def deleteDuplicates(self, head: Optional[ListNode]) -> Optional[ListNode]: | ||
dummy_head = ListNode() | ||
tail = dummy_head | ||
node = head | ||
has_duplicate = False | ||
while node is not None: | ||
if node.next is not None and node.val == node.next.val: | ||
has_duplicate = True | ||
node = node.next | ||
continue | ||
|
||
if has_duplicate: | ||
has_duplicate = False | ||
else: | ||
tail.next = node | ||
tail = tail.next | ||
node = node.next | ||
tail.next = None | ||
return dummy_head.next | ||
``` | ||
```python3 | ||
class Solution: | ||
def deleteDuplicates(self, head: Optional[ListNode]) -> Optional[ListNode]: | ||
dummy_head = ListNode(next=head) | ||
node = dummy_head | ||
while node is not None: | ||
has_duplicate = False | ||
while node.next is not None and node.next.next is not None \ | ||
and node.next.val == node.next.next.val: | ||
node.next = node.next.next | ||
has_duplicate = True | ||
|
||
if has_duplicate: | ||
node.next = node.next.next | ||
else: | ||
node = node.next | ||
return dummy_head.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.
LeetCode で指定されている関数名は lowerCamel なのですが、 Python では一般的には関数名は lower_snake で書くことが多いと思います。lower_camel で書くことをお勧めいたします。
https://peps.python.org/pep-0008/#function-and-variable-names
https://google.github.io/styleguide/pyguide.html#316-naming