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,124 @@
# step1 何も見ずに解く

## 解答
- Set使わなくても書けるはずだが、パッと思いついたのでSetを使って実装
- 意図は理解しやすい気はするが、流石に無駄が多いか
- 計算量
- 時間計算量: O(n)
- 空間計算量: O(n)
- これって2回ループ回しているからO(2n)とすべきですか?
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Big-O記法では定数倍は無視されるためO(n)と書くのが一般的だと思います
https://ja.wikipedia.org/wiki/%E3%83%A9%E3%83%B3%E3%83%80%E3%82%A6%E3%81%AE%E8%A8%98%E5%8F%B7

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.

定数倍として無視ですよね。
今回改めて考えてBig-O記法って極限での振る舞いを考えるものであるため、nが小さい場合には大雑把過ぎるということが腹落ちしました。

```java
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode deleteDuplicates(ListNode head) {
if (head == null) {
return null;
}

// 重複を省いたvalの配列を作成
ListNode node = head;
HashSet<Integer> uniqueValManager = new HashSet<>();
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Managerという名前は何か管理するための機能を持ったクラスのような印象を受けるので、ここでは少し大げさかなと思いました。
単にuniqueValuesなどしたいのですが、下の配列の名前とちょっと被ってしまいそうですね…
uniqueValuesSet, uniqueValuesListなどにするとかでしょうか。

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

単にvisitedとかseenとかでもいいと思います。

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.

ユニークな値を管理するというニュアンスで名付けましたが、変に説明的にし過ぎるよりvisitedなどシンプルな命名の方が直感的ですね。ありがとうございます!

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

unique な値を管理するよというのは HashSet を使う時点で自明なので、visited や seen などの用途に着目したシンプルな名前で十分かなと思います。

A collection that contains no duplicate elements.

https://docs.oracle.com/javase/8/docs/api/java/util/Set.html
https://docs.oracle.com/javase/8/docs/api/java/util/HashSet.html

ArrayList<Integer> uniqueVals = new ArrayList<>();
Comment on lines +29 to +30
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

new_head と new_node を定義すれば、このあたりの変数はまるっと消せそうな感じがします。
https://github.com/kazizi55/coding-challenges/pull/3/files#diff-153271ae97af52ab3609bb5e1cd3215deb7bc0ef64f6750b2220ed75868283f4

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.

最初に思い浮かんだのがSetを利用した実装でしたが、Set使わなくても実装できます。
非破壊での実装例のご紹介ありがとうございます!
この問題の非破壊での実装は試してなかったです。参考になります!

while (node != null) {
if (!uniqueValManager.contains(node.val)) {
uniqueValManager.add(node.val);
uniqueVals.add(node.val);
}
node = node.next;
}

// valの配列を元に繋ぎ直す
ListNode prevNode = 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.

「前のノード」というのは書き手の視点から見た名前で、これは実際には「重複を排除した新しい連結リスト」を作っていっているのでそのことが分かる名前にされていると読み手にとって分かりやすいかなと思いました。

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.

新しいLinked listの末尾を指すのでnewTailなどとするのが分かりやすいでしょうか。ありがとうございます!

prevNode.next = null;
// 配列の先頭の値は追加不要なので 1 からスタート
for (int i = 1; i < uniqueVals.size(); i++) {
prevNode.next = new ListNode(uniqueVals.get(i));
prevNode = prevNode.next;
}
return 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.

最初の head だけ既存のものを使い、それ以降は新しい node をつなげているのって何か理由があるのでしょうか?

headも含め、完全に新しい LinkedList として作成する方が自然な気がします。

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.

headを再利用することで重複削除後のLinkedListの先頭を保持する変数の宣言を省きたかったからだと思います。
とはいえ現状の実装は破壊/非破壊が一貫してなくて見返してみると不自然で気持ち悪いですね。
仰る通り一貫させたほうが自然だと思います。ありがとうございます!

}
}
```

# step2 他の方の解答を見る
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 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.

確かにその方が便利ですね。
今後リンクを記載するようにしてみます。
ありがとうございます!

当初書こうとした時はprevNode保持してとか考えたが、`node.next.next`使えばよかった
## 解答
- 1重ループ版
- 計算量
- 時間計算量: O(n)
- 空間計算量: O(1)
- `continue`で書くか、`if-else`で書くか悩む
- `continue;`を使い、`while`ループの末尾で必ず`node`を更新する方が事故が起こりづらそう
```java
class Solution {
public ListNode deleteDuplicates(ListNode head) {
if (head == null) {
return null;
}

ListNode node = head;
while (node != null && node.next != null) {
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がnullになることがないので、node != null は不要かもしれません。

if (node.val == node.next.val) {
node.next = node.next.next;
continue;
}
node = node.next;
}
return head;
}
}
```

## 解答2
- 2重ループ版
```java
class Solution {
public ListNode deleteDuplicates(ListNode head) {
if (head == null) {
return null;
}

ListNode node = head;
while (node != null) {
// valが重複するならnextを上書き
while (node.next != null && node.val == node.next.val) {
node.next = node.next.next;
}
node = node.next;
}
return head;
}
}
```

# step3 3回ミスなく書く
- 1重ループの方が好みなのでそちらで実装。
## 解答
```java
class Solution {
public ListNode deleteDuplicates(ListNode head) {
if (head == null) {
return null;
}

ListNode node = head;
while (node != null && node.next != null) {
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がnullになるより前に先にnode.nextがnullになるのでwhile (node.next != null)でも動きそうです。
ただそれだと読み手としてちょっと不安(下でnode.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.

https://github.com/kt-from-j/leetcode/pull/3/files#r2342761571
で同じコメントしちゃってました 🙏

nanae772さんのコメントも確かにと思う一方、個人的には冗長な条件式はない方が良い気持ちですね…

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.

冗長なのは承知しているのですが、個人的にはこの書き方が好きです。
とはいえ実務上はコードベースの流儀に合わせてということになると思います。

他の視点を提供していただきありがとうございます!

if (node.val == node.next.val) {
node.next = node.next.next;
continue;
}
node = node.next;
}
return head;
}
}
```