From f8c5d4593b6763148c159c717ea6be7aac10a4e5 Mon Sep 17 00:00:00 2001 From: Taito Ohsumi Date: Mon, 25 Nov 2024 16:59:53 +1100 Subject: [PATCH 1/4] Add 82. Remove Duplicates from Sorted List II.md --- 82. Remove Duplicates from Sorted List II.md | 241 +++++++++++++++++++ 1 file changed, 241 insertions(+) create mode 100644 82. Remove Duplicates from Sorted List II.md 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..5bf7724 --- /dev/null +++ b/82. Remove Duplicates from Sorted List II.md @@ -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) + 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 +``` \ No newline at end of file From da9f3829b98bbafd9d995fc0749f11e0983e1e78 Mon Sep 17 00:00:00 2001 From: Taito Ohsumi Date: Wed, 4 Dec 2024 12:46:29 +1100 Subject: [PATCH 2/4] Add step 4 --- 82. Remove Duplicates from Sorted List II.md | 60 ++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/82. Remove Duplicates from Sorted List II.md b/82. Remove Duplicates from Sorted List II.md index 5bf7724..2830353 100644 --- a/82. Remove Duplicates from Sorted List II.md +++ b/82. Remove Duplicates from Sorted List II.md @@ -238,4 +238,64 @@ class Solution: 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 ``` \ No newline at end of file From b9a5383d88ccb72dc26c5ac735a8d5c5167e9f4e Mon Sep 17 00:00:00 2001 From: Taito Ohsumi Date: Thu, 19 Dec 2024 11:02:05 +1100 Subject: [PATCH 3/4] Update step 4 --- 82. Remove Duplicates from Sorted List II.md | 49 ++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/82. Remove Duplicates from Sorted List II.md b/82. Remove Duplicates from Sorted List II.md index 2830353..d6a2e7a 100644 --- a/82. Remove Duplicates from Sorted List II.md +++ b/82. Remove Duplicates from Sorted List II.md @@ -298,4 +298,53 @@ class Solution: 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 ``` \ No newline at end of file From 5ec3b58f7cf671776bf2f854d31b5acd47c5880b Mon Sep 17 00:00:00 2001 From: Taito Ohsumi Date: Mon, 10 Feb 2025 22:40:03 +1100 Subject: [PATCH 4/4] Add other solutions --- 82. Remove Duplicates from Sorted List II.md | 49 ++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/82. Remove Duplicates from Sorted List II.md b/82. Remove Duplicates from Sorted List II.md index d6a2e7a..4c982c0 100644 --- a/82. Remove Duplicates from Sorted List II.md +++ b/82. Remove Duplicates from Sorted List II.md @@ -347,4 +347,53 @@ class Solution: 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