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
280 changes: 280 additions & 0 deletions medium/82/answer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,280 @@
# Step1

かかった時間:13min

計算量: 計算量: Nをノード数として

時間計算量: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(val=-1, next=head)
prev = dummy
current = head
while current:
while current.next and current.val == current.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.

これ、とりあえず、while 突入して、あとから何が起きたかを考えるの、素直じゃない気がします。後でどうせ分岐するならば確認してから突入したほうが分かりやすくないですか。

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.

有難うございます。

        while curr_node:
            if not curr_node.next or curr_node.val != curr_node.next.val:
                prev_node = prev_node.next
                curr_node = curr_node.next
                continue

こんな感じで、今のノードと次のノードの値が異なる場合の判定を先に済ませる感じでしょうか。

同じノードを飛ばす処理の方に意識がいってしまって、このような構成になってしまったんだと思います。。

Copy link
Copy Markdown
Owner Author

@TORUS0818 TORUS0818 May 5, 2024

Choose a reason for hiding this comment

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

同じような話で、

        while curr_node and curr_node.next:
            if curr_node.val != curr_node.next.val:
                prev_node = prev_node.next
                curr_node = curr_node.next
                continue

こっちの方が良いですかね。

current = current.next
if prev.next == current:
prev = prev.next
current = current.next
else:
prev.next = current.next
current = current.next

return dummy.next
```
思考ログ:
- 83と同じような方針で進めるが、ポインタが一つだと足りなそう
- そういえばdummyポインタを置いて、currentと2つを動かす方法があったなと朧げながら思い出す
- あとは(自分の中で)自然な流れをコードに落とし込む
- ```current```を使って、次のノード以降でどこまで値の重複があるか確認していく(最大最終ノードまで)
- 重複がないパターン(現在のノードと次のノードの値が異なる)
- この場合は```prev```と```current```を一つずつ進める
- 重複ありのパターン(現在のノードと次以降のノードが一つ以上重複している)
- ```current```を重複がなくなる一歩手前のノードまで進める
- ```prev.next```を```current.next```に繋ぐ
- ```current```を一つ進める
- 最初、重複ありのパターンで```prev```を進めるつもりで```prev = current```としてハマった
- LinkedListの逐次処理で何が起きているのか、よく分かっていない(のでこういうことが起きる)
- 上記のように書くと何が起きるのか、一度図示してみる(discordにあげる)

# Step2

講師役目線でのセルフツッコミポイント:
- 変数名をもう少しなんとかしようという件
- ```prev```だけ略語なのもバランス悪い
- 再帰で書ける?

参考にした過去ログなど:
- https://discordapp.com/channels/1084280443945353267/1228700203327164487/1228789691881623704
- https://discordapp.com/channels/1084280443945353267/1227073733844406343/1228549047451910284
- 再帰の実装例
- 重複検知処理を関数として切り出す
- https://discordapp.com/channels/1084280443945353267/1221030192609493053/1225103398962204686
- https://discordapp.com/channels/1084280443945353267/1192736784354918470/1222902863148093502
- https://discordapp.com/channels/1084280443945353267/1200089668901937312/1206627151969787985
- while1つで回す実装

上記を踏まえて、まず変数名を少し変えてみたバージョン
```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_node = ListNode(-1, head)
previous_node = dummy_node
current_node = head
while current_node:
while current_node.next and current_node.val == current_node.next.val:
current_node = current_node.next

if previous_node.next == current_node:
previous_node = previous_node.next
current_node = current_node.next
else:
previous_node.next = current_node.next
current_node = current_node.next

return dummy_node.next
```
思考ログ:
- 変数名が長いので```prev_node```、```curr_node```のように省略形で揃えてもいいかも

漁った過去ログでもあった、whileを二重にしない方式の解法を再現してみる
```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(-1)
previous = dummy
current = head
Comment on lines +102 to +103
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

previous_nodenodeでもよいかもしれません。

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.

有難うございます!
文字数も減りますし、こちらの方が良いですね。

duplicated_value = None
while current:
if current.val == duplicated_value:
current = current.next
continue
if current.next and current.val == current.next.val:
duplicated_value = current.val
continue

previous.next = ListNode(current.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.

ここで新たなノードを作っていますが、previous.next = currentにするとLinkedListの繋ぎ変え操作だけで行けると思います。
ただ、whileの抜けた部分でもprevious.next = currentする必要がありますが。

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.

有難うございます。
個人的にループの外で後始末したくないなという気持ちがありますが、オブジェクトを毎回生成するのも無駄ですよね。

previous.next = currentがないと、最後のノードの値が連続したようなケース(1 -> 2 -> 2 -> 2とか)でコケる感じですね。

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

確かに私も抜けた後にprevious.next = currentするのはできればしたくないと思います。
ただ、新しいノードを作る方法だと空間計算量がO(N)になるのがデメリットかなと思います。

previous = previous.next
current = current.next

return dummy.next
```
思考ログ:
- 変数名はサボった
- ポイントは重複検知した際にその値を持っておくところ

再帰で書いてみる
```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]:
def delete_duplicates_helper(
head: Optional[ListNode],
duplicated_val: int
) -> Optional[ListNode]:
if not head:
return head

if head.val == duplicated_val:
head = delete_duplicates_helper(head.next, head.val)
elif head.next and head.val == head.next.val:
head = delete_duplicates_helper(head.next, head.val)
else:
head.next = delete_duplicates_helper(head.next, None)

return head
Comment on lines +139 to +146
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

early returnしちゃって

            if head.val == duplicated_val:
                return delete_duplicates_helper(head.next, head.val)
            if head.next and head.val == head.next.val:
                return delete_duplicates_helper(head.next, head.val)
            head.next = delete_duplicates_helper(head.next, None)
            return head

するのはいかがでしょうか。

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.

有難うございます。
確かにこちらの方が条件ごとにシンプルに分かれていて読みやすい気がします。

今までearly returnが選択肢の候補に入ることがあまりなかったので、今後は意識してみようと思います。


unique_head = delete_duplicates_helper(head, None)
return unique_head
```
思考ログ:
- 再帰する時にどうやって重複する情報を持たせようか悩んだ
- 問題は、```head```と重複削除した```head.next```を接続する部分
- 83と違って、今回の重複削除では重複したノードは全て削除される(よって、重複処理済みのLinkedListを見ただけでは、重複したものが何だったか伝わらない)
- 例えば、```1>1>1>2```のようなLinkedListを考えて、```1```と```1>1>2```を重複処理したものの接続を考えてみる
- この場合、後者の重複処理をすると、```2```となるので、結果は```1>2```となってしまう(期待してるのは```2```)
- なので、```1```が重複してるという情報を引き継がないとうまくいかない
- まだ再帰に苦手意識がある、もう少し数を熟そう

# Step3

かかった時間:3min

```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_node = ListNode(-1, head)
prev_node = dummy_node
curr_node = head
while curr_node:
while curr_node.next and curr_node.val == curr_node.next.val:
curr_node = curr_node.next

if prev_node.next == curr_node:
prev_node = prev_node.next
else:
prev_node.next = curr_node.next
curr_node = curr_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.

このStep 3は読みにくいと感じました。重複値がある場合とない場合の2つを意識して読み進める必要がありますよね。

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.

有難うございます。

#6 (comment)
こちらのご指摘も同様ですよね。

先に重複値がない場合を処理してしまって、後半で重複のある場合の処理をすることで、問題をシンプルにしていくイメージでしょうか。


return dummy_node.next
```
思考ログ:
- 最後の分岐の```curr_node = curr_node.next```の部分は共通なので外に出した

# Step4

変数名を修正
```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]:
# 10:41
dummy_node = ListNode(-1, head)
prev_node = dummy_node
node = head
while node and node.next:
if node.val != node.next.val:
prev_node = prev_node.next
node = node.next
continue

while node.next and node.val == node.next.val:
node = node.next

prev_node.next = node.next
node = node.next

return dummy_node.next
```
思考ログ:
- なるべくシンプルに意味のある名前をつけることを心がける

再帰の実装の見直し
```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]:
# 13:26
def delete_duplicates_helper(head, duplicated_value):
if not head:
return head

if (head.next and head.val == head.next.val) or \
(head.val == duplicated_value):
return delete_duplicates_helper(head.next, head.val)

head.next = delete_duplicates_helper(head.next, None)
return head

return delete_duplicates_helper(head, None)
```
思考ログ:
- early-returnができないか、考える
- ノードを飛ばす時の処理という括りでif文をまとめた
- これは可読性の観点からは分けといても良かったかもしれない

while一回版の書き直し(新しいオブジェクト使わないパターン)
```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_node = ListNode(-1, head)
prev_node = dummy_node
node = head
duplicated_val = None
while node:
if node.next and node.val == node.next.val:
duplicated_val = node.val
node = node.next
continue
if node.val == duplicated_val:
node = node.next
continue

prev_node.next = node
prev_node = prev_node.next
node = node.next

prev_node.next = node
return dummy_node.next
```
思考ログ:
- ここもcontinueし忘れてif-elseのでっかい塊を作りがちなので注意