Skip to content
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
問題文: https://leetcode.com/problems/remove-duplicates-from-sorted-list-ii/

# step1: 何も見ないで書く(制限時間5分)
- 時間計算量O(n)、空間計算量O(1)
- 新井さんの解説を丸写しした。正直コードは読めていない。
- 小田さんが言うところの仕事の引継ぎという発想が欠けていることを痛感した。
```python
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def deleteDuplicates(self, head: Optional[ListNode]) -> Optional[ListNode]:
dummy = ListNode(next = head)
node = dummy
while node.next is not None and node.next.next is not None:
if node.next.val == node.next.next.val:
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

一般にネストを浅くしたほうが読みやすくなる傾向があります。真偽を逆にして、ネストを浅くするとよいと思います。

if node.next.val != node.next.next.val:
    continue

copy = node.next
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

copyモジュールがあるので、copyという変数名は避けたほうがよいかもしれません。

while copy.next is not None and copy.next.val == copy.val:
copy.next = copy.next.next
node.next = copy.next
else:
node = node.next
return dummy.next

```
# step2: コードを整える
## 2-1
- 毎回のループで引き継ぐもの
1. 重複無しのノード: このグループの末尾を`latest_unique`で保持。
2. 重複未チェックのノード: このグループの先頭のノードは`latest_unique.next`。これがループの主役なので`checking`と名付けて保持。
- skip_duplicates()に入力エラー処理が必要か迷ったが、`None`が入力されることはないのでとりあえずこのまま。
```python
class Solution:
def deleteDuplicates(self, head: Optional[ListNode]) -> Optional[ListNode]:
def skip_duplicates(node):
while node.next is not None and node.next.val == node.val:
node.next = node.next.next
return node.next
dummy = ListNode(next = head)
latest_unique = dummy
checking = latest_unique.next
while checking is not None and checking.next is not None:
if checking.val != checking.next.val:
latest_unique = checking
else:
latest_unique.next = skip_duplicates(checking)
checking = latest_unique.next
return dummy.next

```
## 2-2
- 2-1のコードをさらに整理。参考にした議論はdiscordのリンクを保存してなかったので引用できないが、小田さんがいろいろ突っ込みを入れていて19個目のつっこみで条件式を色々と変形していたあたりを参考にした。
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

- こっちのほうが読みやすい。ネストが浅いし、重複を削除するときとしないときでは違う処理をしないといけないが、それらが違うブロックにまとまっている。
```python
class Solution:
def deleteDuplicates(self, head: Optional[ListNode]) -> Optional[ListNode]:
def skip_duplicates(node):
while node.next is not None and node.next.val == node.val:
node.next = node.next.next
return node.next
dummy = ListNode(next = head)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

inner functionの定義の後には空行を入れたいなと思いましたが、好みかもしれません。

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

inner functionの定義の後には空行を入れたいなと思いました

同じように感じました

latest_unique = dummy
checking = latest_unique.next
while checking is not None and checking.next is not None:
if checking.val != checking.next.val:
latest_unique = checking
checking = latest_unique.next
continue
latest_unique.next = skip_duplicates(checking)
checking = latest_unique.next
return dummy.next

```

# step3: ミスなく3回書く(制限時間10分)
```python
class Solution:
def deleteDuplicates(self, head: Optional[ListNode]) -> Optional[ListNode]:
def skip_duplicates(node):
while node.next is not None and node.next.val == node.val:
node.next = node.next.next
return node.next
Comment on lines +81 to +84
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

単に重複を飛ばすだけならnode.nextを繋ぎかえていくのではなく、nodeを動かしていく方法のほうが分かりやすいかなと感じました。
nextを繋ぎ変えないのでこの関数の中では副作用が無くなり、そういった点でも良いかなと思います。

Suggested change
def skip_duplicates(node):
while node.next is not None and node.next.val == node.val:
node.next = node.next.next
return node.next
def skip_duplicates(node):
duplicated_val = node.val
while node is not None and node.val == duplicated_val:
node = node.next
return node

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

修正後の方がいいですね。手抜きして関数化する前のコードをそのまま関数のコードに流用しておりました。

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

nodeを動かしていく方法のほうが分かりやすいかなと感じました

同感です。修正後の方が私も良いと思います。

dummy = ListNode(next = head)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

PEP8ではキーワード引数への代入演算子の前後にはスペースを入れないことになっています。ご参考まで。

Don’t use spaces around the = sign when used to indicate a keyword argument, or when used to indicate a default value for an unannotated function parameter:

https://peps.python.org/pep-0008/#other-recommendations

latest_unique = dummy
checking = latest_unique.next
while checking is not None and checking.next is not None:
if checking.val != checking.next.val:
latest_unique = checking
checking = latest_unique.next
continue
latest_unique.next = skip_duplicates(checking)
checking = latest_unique.next
Comment on lines +85 to +94
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

latest_uniquechecking の責務が混ざって見えます。
dummy/latest_unique は「確定済みリスト」、checking は「未確定リストの走査」に役割分担し、latest_unique.next は常に None に保つ不変条件にすると、意図が明確になります。下記のようにすると、ユニーク確定時だけ連結し、それ以外では確定側に触れません。

class Solution:
    def deleteDuplicates(self, head: Optional[ListNode]) -> Optional[ListNode]:
        def skip_duplicates(node: ListNode) -> Optional[ListNode]:
            while node.next is not None and node.next.val == node.val:
                node.next = node.next.next
            return node.next

        dummy = ListNode()          # 確定済みリストの番兵
        latest_unique = dummy       # その末尾。常に latest_unique.next は None
        checking = head             # 未確定側の走査ポインタ

        while checking is not None:
            # 直後が別値 or 末尾ならユニーク
            if checking.next is None or checking.val != checking.next.val:
                latest_unique.next = checking   # 確定側に連結
                latest_unique = checking        # 末尾を更新
                checking = checking.next        # 未確定側を先に進める
                latest_unique.next = None       # 不変条件: ここで切断
            else:
                # 重複ブロックは確定側に触れずスキップ
                checking = skip_duplicates(checking)

        return dummy.next

Copy link
Copy Markdown
Owner Author

@TrsmYsk TrsmYsk Oct 5, 2025

Choose a reason for hiding this comment

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

ご指摘の通りlatest_uniquecheckingの役割を分け切れていなかったです。番兵というテクニックを初めて知りました。

return dummy.next

```