Skip to content

82. Remove Duplicates from Sorted List II #19

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

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
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
250 changes: 245 additions & 5 deletions 0082_Remove_Duplicates_from_Sorted_List_2/solution_ja.md
Original file line number Diff line number Diff line change
@@ -1,33 +1,273 @@
## Problem
// The URL of the problem

https://leetcode.com/problems/remove-duplicates-from-sorted-list-ii/

## Step 1
5分程度答えを見ずに考えて、手が止まるまでやってみる。

5 分程度答えを見ずに考えて、手が止まるまでやってみる。
何も思いつかなければ、答えを見て解く。ただし、コードを書くときは答えを見ないこと。
動かないコードも記録する。
正解したら一旦OK。思考過程もメモする。
正解したら一旦 OK。思考過程もメモする。

### Approach
*

- 3 回の独立したループを実行
- 1 回目: リストを走査して、対象ノードの値と次のノードの値とが重複している場合は、値を HashSet に格納
- 2 回目: 再度頭から走査。先頭が重複ノードだった場合に備え、重複していないノードまで進める
- 3 回目: 改めて新しい先頭から走査。HashSets を確認しながら値重複ノードはスキップしつつ、重複がないノード同士をつなげ直す
- 感想
- なんとなくもっと見やすくて効率の良い方法がありそうなモヤモヤがありながらも、思いつかなかったので上記の発想で走りきったという感じ
Copy link

Choose a reason for hiding this comment

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

このあたりにどう引き継ぐかどう考えるかを書いてあったと思います。
https://docs.google.com/document/d/11HV35ADPo9QxJOpJQ24FcZvtvioli770WWdZZDaLOfg/edit?tab=t.0

結局のところ、わりと、色々な方法があるんですが、どういう状態で引き継いだのか、なんですよね。


```java
class Solution {
public ListNode deleteDuplicates(ListNode head) {
if (head == null) {
return null;
}
ListNode node = head;
HashSet<Integer> duplicateVals = new HashSet<>();

while (node != null && node.next != null) {
Copy link

Choose a reason for hiding this comment

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

head はすでに null かどうかのチェックをしているため、
while (node.next != null) {
で十分だと思いました。

Copy link
Owner Author

Choose a reason for hiding this comment

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

たしかにそうですね。気付きませんでした。

if (node.val == node.next.val) {
duplicateVals.add(node.val);
}
node = node.next;
}

node = head;
while (node != null && duplicateVals.contains(node.val)) {
if (node.next == null) { // All nodes are duplicated.
node = null;
Copy link

Choose a reason for hiding this comment

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

ここですべてのノードの値が重複しているので、ここで return null; としてしまってよいと思いました。

break;
}
node = node.next;
}

head = node;
ListNode lastUnique = head;
if (node != null) {
node = node.next;
}
while (node != null) {
if (duplicateVals.contains(node.val)) {
lastUnique.next = null;
} else {
lastUnique.next = node;
lastUnique = node;
Copy link

Choose a reason for hiding this comment

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

lastUniquelastUnique.next の両方に同じ node を代入している点に違和感を感じました。

lastUnique.next = node;
lastUnique = lastUnique.next;

としたほうが、 lastUnique を進めている感じが出て、分かりやすくなると思います。

Copy link
Owner Author

Choose a reason for hiding this comment

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

おっしゃるとおりですね。この方が直感的ですね。

}
node = node.next;
}
return head;
}
}
```

- https://github.com/katsukii/leetcode/pull/19/files#r2050477054
- 以下の方が直感的

```java
lastUnique.next = node;
lastUnique = node;
```

## Step 2

他の方が描いたコードを見て、参考にしてコードを書き直してみる。
参考にしたコードのリンクは貼っておく。
読みやすいことを意識する。
他の解法も考えみる。

### Approach 1

- 3 つのポインタを使用
- node: メインでリストを一つずつ走査するノード。head からスタート
- dummyHead: 番兵として元のリストの head の手前に配置する
- 番兵 を先頭に置くことで、本来の head 自体が削除対象となるケース(1->1->2 など)を考慮したループ処理が不要になる
Copy link

Choose a reason for hiding this comment

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

置かないと分岐が増えるけれども、それでも解くことはできます。

Copy link
Owner Author

Choose a reason for hiding this comment

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

実装してみました。やはりdummyHeadがあった方が書きやすいと思いました。

- lastUnique: 重複のない最後のノードを追跡。最初は dummyHead
- リストを走査し一つ先のノードの値をチェックしていく。重複ノードはスキップ、重複なしノードには lastUnique と lastUnique.next をリンクさせる
- 上記を繰り返すことで dummyHead.next が必ず重複のないリストの先頭になる

```java
class Solution {
public ListNode deleteDuplicates(ListNode head) {
ListNode node = head;
ListNode dummyHead = new ListNode(0, node);
ListNode lastUnique = dummyHead;

while (node != null) {
if (node.next != null && node.val == node.next.val) { // Node is duplicate
while (node.next != null && node.val == node.next.val) { // Skip to last duplicated node
node = node.next;
}
lastUnique.next = node.next; // Next of last duplicated node
} else { // Node is not Duplicate
lastUnique = lastUnique.next;
}
node = node.next;
}
return dummyHead.next;
}
}
```

### Approach 2

- こちらは dummyHead と node の 2 人の登場人物だけで完了できる方法
- Approach 1 とは逆の発想で、重複なしノードを一気にスキップし、その後重複が現れる限り一つ先につなぎなおすという方法
- 以下を参考にした
- https://github.com/5ky7/arai60/pull/5/files#diff-0c860cd754249868513e4f9054206317fa33d0f548fc3896ac2b3e11822fd852R160-R179
- 感想
- 自らの無意識の選択をメタ認知した上で別の視点はないか探すという意識は常に持っておきたいと思った

```java
class Solution {
public ListNode deleteDuplicates(ListNode head) {
ListNode dummyHead = new ListNode(0, head);
ListNode node = dummyHead;

while (node.next != null) {
Copy link

Choose a reason for hiding this comment

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

node.next.next == null の場合に node = node.next; で node を進めたあと、 while (node.next != null) { でループを抜けようとしている点が、ややパズルに感じました。

自分なら

while (node.next != null && node.next.next != null) {
    if (node.next.val != node.next.next.val) {

と書くと思います。

Copy link
Owner Author

Choose a reason for hiding this comment

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

たしかにこちらの方が自然ですね。ありがとうございます。

if (node.next.next == null || node.next.val != node.next.next.val) {
node = node.next;
continue;
}

int duplicateVal = node.next.val;
while (node.next != null && node.next.val == duplicateVal) {
node.next = node.next.next;
}
}

return dummyHead.next;
}
}
```

- https://github.com/katsukii/leetcode/pull/19/files#r2050481915
- > node.next.next == null の場合に node = node.next; で node を進めたあと、 while (node.next != null) { でループを抜けようとしている点が、ややパズルに感じました。
- 上記のフィードバックを受け、以下改良版

```java
class Solution {
public ListNode deleteDuplicates(ListNode head) {
ListNode dummyHead = new ListNode(0, head);
ListNode node = dummyHead;

while (node.next != null && node.next.next != null) {
if (node.next.val != node.next.next.val) {
node = node.next;
continue;
}

int duplicateVal = node.next.val;
while (node.next != null && node.next.val == duplicateVal) {
node.next = node.next.next;
}
}

return dummyHead.next;
}
}
```

### その他参考にした PR

- https://github.com/shintaro1993/arai60/pull/7/files
- https://github.com/h1rosaka/arai60/pull/6/files
- https://github.com/shintaroyoshida20/leetcode/pull/7/files

## Step 3

今度は、時間を測りながら、もう一回書く。
アクセプトされたら消すを3回連続できたら問題はOK。
アクセプトされたら消すを 3 回連続できたら問題は OK。

Approach 1

```java
class Solution {
public ListNode deleteDuplicates(ListNode head) {
ListNode node = head;
ListNode dummyHead = new ListNode(0, node);
ListNode lastUnique = dummyHead;

while (node != null) {
if (node.next != null && node.val == node.next.val) { // Node is duplicate
while (node.next != null && node.val == node.next.val) { // Skip to last duplicated node
node = node.next;
}
lastUnique.next = node.next; // Next of last duplicated node
} else { // Node is not Duplicate
lastUnique = lastUnique.next;
}
node = node.next;
}
return dummyHead.next;
}
}

Choose a reason for hiding this comment

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

Copy link
Owner Author

Choose a reason for hiding this comment

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

なるほど、こちらは気づきませんでした。ありがとうございます。

```

## Step 4

> dummyHead 置かないと分岐が増えるけれども、それでも解くことはできます。

というコメントをいただいたので実装してみた。やはり

```java
class Solution {
public ListNode deleteDuplicates(ListNode head) {
// Skip duplicated Node
while (head != null && head.next != null && head.val == head.next.val) {
int dupVal = head.val;
while (head != null && head.val == dupVal) {
head = head.next;
}
}

if (head == null) return null;

ListNode unique = head;
ListNode node = head.next;

while (node != null && node.next != null) {
if (node.val == node.next.val) {
int dupVal = node.val;
while (node != null && node.val == dupVal) {
node = node.next;
}
unique.next = node;
} else {
unique.next = node;
unique = node;
node = node.next;
}
}

return head;
}
}
```

再帰の実装

- 面白い。思った以上にシンプルにいけた

```java
class Solution {
public ListNode deleteDuplicates(ListNode head) {
return deleteDuplicatesHelper(head, -101); // -101: out of val
}

private ListNode deleteDuplicatesHelper(ListNode head, int duplicatedValue) {
if (head == null) {
return null;
}

// Start or Continue of Duplicate
if ((head.next != null && head.val == head.next.val) || head.val == duplicatedValue) {
return deleteDuplicatesHelper(head.next, head.val);
}

head.next = deleteDuplicatesHelper(head.next, -101); // No duplicatedValue
return head;
}
}
```