diff --git a/82. Remove Duplicates from Sorted List II.md b/82. Remove Duplicates from Sorted List II.md new file mode 100644 index 0000000..4c982c0 --- /dev/null +++ b/82. Remove Duplicates from Sorted List II.md @@ -0,0 +1,399 @@ +# 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) + 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 + 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: + 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 +``` + +# step 4 +コメントまとめ +- 切断しておいて繋ぐが、厳密でない。 + +その他 +- backslashで行を繋がない方が良い + - [PEP8](https://peps.python.org/pep-0008/#:~:text=The%20preferred%20way,for%20that%20case%3A) + > The preferred way of wrapping long lines is by using Python’s implied line continuation inside parentheses, brackets and braces. Long lines can be broken over multiple lines by wrapping expressions in parentheses. These should be used in preference to using a backslash for line continuation. + - [Google style guide](https://google.github.io/styleguide/pyguide.html#383-functions-and-methods:~:text=do%20not%20use%20a%20backslash%20for%20explicit%20line%20continuation.) + > Do not use a backslash for explicit line continuation. + +繋いでおいて切断する。 +```python +class Solution: + def deleteDuplicates(self, head: Optional[ListNode]) -> Optional[ListNode]: + dummy_head = ListNode(next=head) + checked_tail = dummy_head + + while checked_tail is not None: + has_duplicate = False + while ( + checked_tail.next is not None + and checked_tail.next.next is not None + and checked_tail.next.val == checked_tail.next.next.val + ): + has_duplicate = True + checked_tail.next = checked_tail.next.next + + if has_duplicate: + checked_tail.next = checked_tail.next.next + continue + checked_tail = checked_tail.next + return dummy_head.next +``` + +切断しておいて繋ぐ +```python +class Solution: + def deleteDuplicates(self, head: Optional[ListNode]) -> Optional[ListNode]: + dummy_head = ListNode() + tail = dummy_head + node = head + + while node is not None: + has_duplicate = False + while node.next is not None and node.val == node.next.val: + has_duplicate = True + node = node.next + + if has_duplicate: + node = node.next + continue + + tail.next = node + tail = tail.next + node = node.next + tail.next = None + return dummy_head.next +``` + +追記)step4の上の二つの解法はduplicateがある場合もない場合も、ネストされたwhile文を通る様になっていて読みにくい。また、その結果として、has_duplicateという変数が必要になっている。duplicateがない場合はノードを適切に先に進めてcontinue、ある場合はノードの繋ぎ換えをするとしたほうが、わかりやすいと思った。 + +繋いでおいて切断 +```python +class Solution: + def deleteDuplicates(self, head: Optional[ListNode]) -> Optional[ListNode]: + dummy_head = ListNode(next=head) + checked_tail = dummy_head + + while checked_tail is not None: + if ( + checked_tail.next is None + or checked_tail.next.next is None + or checked_tail.next.val != checked_tail.next.next.val + ): + checked_tail = checked_tail.next + continue + + duplicated_value = checked_tail.next.val + while ( + checked_tail.next is not None + and checked_tail.next.val == duplicated_value + ): + checked_tail.next = checked_tail.next.next + return dummy_head.next + +``` + +切断しておいて繋ぐ +```python +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 + tail.next = None + continue + + duplicated_value = node.val + while node is not None and node.val == duplicated_value: + node = node.next + return dummy_head.next +``` + +再帰 +```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 + + duplicated_value = head.val + while head is not None and head.val == duplicated_value: + head = head.next + return self.deleteDuplicates(head) +``` + +dummyを用いない.似たような判定をheadとそれ以外で分けてしているのでコードが長くなってしまう。 +書いてみると想像していたほどには、めんどくさくはなかった。 +```python +class Solution: + def deleteDuplicates(self, head: Optional[ListNode]) -> Optional[ListNode]: + def skip_duplicates(node: ListNode) -> Optional[ListNode]: + duplicated_value = node.val + while node is not None and node.val == duplicated_value: + node = node.next + return node + + while ( + head is not None + and head.next is not None + and head.val == head.next.val + ): + head = skip_duplicates(head) + + tail = head + while tail is not None: + if ( + tail.next is None + or tail.next.next is None + or tail.next.val != tail.next.next.val + ): + tail = tail.next + continue + + tail.next = skip_duplicates(tail.next) + return head +``` \ No newline at end of file