-
Notifications
You must be signed in to change notification settings - Fork 0
82. remove duplicates from sorted list ii #4
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| # step1 | ||
| 83と同様に、確定しているノードの次のノードの状態で場合分けして考える | ||
|
|
||
| tailまで確定しているとして(最初の場合分けも必要か?)、 | ||
| - tail.next == nullptr | ||
| - tail.next が採用できる=値が連続していない | ||
| - tail.next が採用できない=tail.nextから値が連続している | ||
|
|
||
| が考えられる。番兵をおけば最初を場合分けなくて済む。 | ||
|
|
||
| →ややこしくなってしまいとりあえず値のカウントリストを用意して解いた。 | ||
| # step2 | ||
| 新井さんの解答ではやはり重複中を走るポインタをもう一つ用意していたのでそうすればすっきり実装できそう。 | ||
|
|
||
| →discordを見て削除は値で行うことに変更。 | ||
|
|
||
| 小田さんの電車の振り分けの比喩で考えると、各日付の作業者は各日付のうちに重複を取り除くことにしtailを引き継ぎのない重複のない状態にして次の人に引き継ぐということ。 | ||
|
|
||
|
|
||
| # step3 | ||
| 一重目のwhileで注目しているノードをcur_nodeで置いたけどあんまりわかりやすくなっていない? |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| #include <map> | ||
| struct ListNode { | ||
| int val; | ||
| ListNode* next; | ||
| ListNode() : val(0), next(nullptr) {} | ||
| ListNode(int x) : val(x), next(nullptr) {} | ||
| ListNode(int x, ListNode* next) : val(x), next(next) {} | ||
| }; | ||
|
|
||
| class Solution { | ||
| public: | ||
| ListNode* deleteDuplicates(ListNode* head) { | ||
| // 値の登場回数を管理する | ||
| std::map<int, int> count; | ||
| for (ListNode* p = head; p != nullptr; p = p->next) { | ||
| count[p->val]++; | ||
| } | ||
| ListNode* dummy = new ListNode(0); | ||
| dummy->next = head; | ||
| ListNode* tail = dummy; | ||
| while (tail != nullptr && tail->next != nullptr) { | ||
| if (count[tail->next->val] > 1) { | ||
| tail->next = tail->next->next; | ||
| } else { | ||
| tail = tail->next; | ||
| } | ||
| } | ||
| ListNode* ans = dummy->next; | ||
| delete dummy; | ||
| return ans; | ||
| } | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| struct ListNode { | ||
| int val; | ||
| ListNode* next; | ||
| ListNode() : val(0), next(nullptr) {} | ||
| ListNode(int x) : val(x), next(nullptr) {} | ||
| ListNode(int x, ListNode* next) : val(x), next(next) {} | ||
| }; | ||
|
|
||
| class Solution { | ||
| public: | ||
| ListNode* deleteDuplicates(ListNode* head) { | ||
| // スタック上に動的確保することで開放しなくてよくなる | ||
| ListNode dummy(0, head); | ||
| ListNode* tail = &dummy; | ||
| // tailまでは重複がないとして次のノードから確認する | ||
| while (tail->next && tail->next->next) { | ||
| if (tail->next->val != tail->next->next->val) { | ||
| tail = tail->next; | ||
| } else { | ||
| // 重複を検知した場合、値が被っている間ノードを消していく | ||
| int val_to_remove = tail->next->val; | ||
| while (tail->next && tail->next->val == val_to_remove) { | ||
| ListNode* toDelete = tail->next; | ||
| tail->next = tail->next->next; | ||
| delete toDelete; | ||
| } | ||
| } | ||
| } | ||
| return dummy.next; | ||
| } | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| struct ListNode { | ||
| int val; | ||
| ListNode* next; | ||
| ListNode() : val(0), next(nullptr) {} | ||
| ListNode(int x) : val(x), next(nullptr) {} | ||
| ListNode(int x, ListNode* next) : val(x), next(next) {} | ||
| }; | ||
|
|
||
| class Solution { | ||
| public: | ||
| ListNode* deleteDuplicates(ListNode* head) { | ||
| ListNode dummy(0, head); | ||
| ListNode* tail = &dummy; | ||
| while (tail->next && tail->next->next) { | ||
| ListNode* cur_node = tail->next; | ||
| if (cur_node->val != cur_node->next->val) { | ||
| tail = cur_node; | ||
| continue; | ||
| } | ||
| int val_to_remove = cur_node->val; | ||
| while (cur_node && cur_node->val == val_to_remove) { | ||
| ListNode* to_delete = cur_node; | ||
| cur_node = cur_node->next; | ||
| delete to_delete; | ||
| } | ||
| tail->next = cur_node; | ||
| } | ||
| return dummy.next; | ||
| } | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| struct ListNode { | ||
| int val; | ||
| ListNode* next; | ||
| ListNode() : val(0), next(nullptr) {} | ||
| ListNode(int x) : val(x), next(nullptr) {} | ||
| ListNode(int x, ListNode* next) : val(x), next(next) {} | ||
| }; | ||
|
|
||
| class Solution { | ||
| public: | ||
| ListNode* deleteDuplicates(ListNode* head) { | ||
| ListNode dummy(-1, head); | ||
| ListNode* last_non_duplicate_node = &dummy; | ||
| ListNode* cur_node = last_non_duplicate_node->next; | ||
| while (cur_node && cur_node->next) { | ||
| if (cur_node->val != cur_node->next->val) { | ||
| last_non_duplicate_node = cur_node; | ||
| cur_node = cur_node->next; | ||
| continue; | ||
| } | ||
| int val_to_remove = cur_node->val; | ||
| while (cur_node && cur_node->val == val_to_remove) { | ||
| ListNode* to_delete = cur_node; | ||
| cur_node = cur_node->next; | ||
| delete to_delete; | ||
| } | ||
| last_non_duplicate_node->next = cur_node; | ||
| } | ||
| return dummy.next; | ||
| } | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| #include <memory> | ||
|
|
||
| struct ListNode { | ||
| int val; | ||
| std::unique_ptr<ListNode> next; | ||
| ListNode() : val(0), next(nullptr) {} | ||
| ListNode(int x) : val(x), next(nullptr) {} | ||
| ListNode(int x, std::unique_ptr<ListNode> next) : val(x), next(std::move(next)) {} | ||
| }; | ||
|
|
||
| class Solution { | ||
| public: | ||
| std::unique_ptr<ListNode> removeDuplicate(std::unique_ptr<ListNode> head) { | ||
| std::unique_ptr<ListNode> dummy = std::make_unique<ListNode>(0); | ||
| dummy->next = std::move(head); | ||
| std::unique_ptr<ListNode>* tail = &dummy->next; | ||
|
|
||
| while (*tail && (*tail)->next) { | ||
| if ((*tail)->val != (*tail)->next->val) { | ||
| tail = &(*tail)->next; | ||
| } else { | ||
| int val_to_remove = (*tail)->val; | ||
| while (*tail && (*tail)->val == val_to_remove) { | ||
| *tail = std::move((*tail)->next); // 外れたノードは自動で delete | ||
| } | ||
| } | ||
| } | ||
| return std::move(dummy->next); | ||
| } | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| // ポインタと参照の違い | ||
|
|
||
| #include <iostream> | ||
| void func(int& x) { | ||
| x += 10; | ||
| printf("%p\n", &x); | ||
| } | ||
| int main() { | ||
| int b = 10; | ||
| int& c = b; | ||
| std::cout << c << std::endl; // 10 | ||
| printf("%p\n", &b); | ||
| func(b); // bは20になる | ||
| std::cout << b << std::endl; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| # step1 | ||
| 83と同様に、確定しているノードの次のノードの状態で場合分けして考える | ||
|
|
||
| tailまで確定しているとして(最初の場合分けも必要か?)、 | ||
| - tail.next == nullptr | ||
| - tail.next が採用できる=値が連続していない | ||
| - tail.next が採用できない=tail.nextから値が連続している | ||
|
|
||
| が考えられる。番兵をおけば最初を場合分けなくて済む。 | ||
|
|
||
| →ややこしくなってしまいとりあえず値のカウントリストを用意して解いた。 | ||
| # step2 | ||
| 新井さんの解答ではやはり重複中を走るポインタをもう一つ用意していたのでそうすればすっきり実装できそう。 | ||
|
|
||
| →discordを見て削除は値で行うことに変更。 | ||
|
|
||
| 小田さんの電車の振り分けの比喩で考えると、各日付の作業者は各日付のうちに重複を取り除くことにしtailを引き継ぎのない重複のない状態にして次の人に引き継ぐということ。 | ||
|
|
||
|
|
||
| # step3 | ||
| 一重目のwhileで注目しているノードをcur_nodeで置いたけどあんまりわかりやすくなっていない? | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| #include <map> | ||
| struct ListNode { | ||
| int val; | ||
| ListNode* next; | ||
| ListNode() : val(0), next(nullptr) {} | ||
| ListNode(int x) : val(x), next(nullptr) {} | ||
| ListNode(int x, ListNode* next) : val(x), next(next) {} | ||
| }; | ||
|
|
||
| class Solution { | ||
| public: | ||
| ListNode* deleteDuplicates(ListNode* head) { | ||
| // 値の登場回数を管理する | ||
| std::map<int, int> count; | ||
| for (ListNode* p = head; p != nullptr; p = p->next) { | ||
| count[p->val]++; | ||
| } | ||
| ListNode* dummy = new ListNode(0); | ||
| dummy->next = head; | ||
| ListNode* tail = dummy; | ||
| while (tail != nullptr && tail->next != nullptr) { | ||
| if (count[tail->next->val] > 1) { | ||
| tail->next = tail->next->next; | ||
| } else { | ||
| tail = tail->next; | ||
| } | ||
| } | ||
| ListNode* ans = dummy->next; | ||
| delete dummy; | ||
| return ans; | ||
| } | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| struct ListNode { | ||
| int val; | ||
| ListNode* next; | ||
| ListNode() : val(0), next(nullptr) {} | ||
| ListNode(int x) : val(x), next(nullptr) {} | ||
| ListNode(int x, ListNode* next) : val(x), next(next) {} | ||
| }; | ||
|
|
||
| class Solution { | ||
| public: | ||
| ListNode* deleteDuplicates(ListNode* head) { | ||
| // スタック上に動的確保することで開放しなくてよくなる | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: 解放 |
||
| ListNode dummy(0, head); | ||
| ListNode* tail = &dummy; | ||
| // tailまでは重複がないとして次のノードから確認する | ||
| while (tail->next && tail->next->next) { | ||
| if (tail->next->val != tail->next->next->val) { | ||
| tail = tail->next; | ||
| } else { | ||
| // 重複を検知した場合、値が被っている間ノードを消していく | ||
| int val_to_remove = tail->next->val; | ||
| while (tail->next && tail->next->val == val_to_remove) { | ||
| ListNode* toDelete = tail->next; | ||
| tail->next = tail->next->next; | ||
| delete toDelete; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ここで delete してよいか微妙に思いました。 仮に呼び出す側の立場に立って考えた場合、 ListNode* を引数として渡す関数がある場合、渡すインスタンスを呼び出し側で保持し続けるべきか、関数の中で解放されるのかが気になると思います。このあたりは関数の仕様として、関数のコメントに書かれていて欲しく思います。 仮に前者だった場合、関数の中で delete すると、二重解放になり、異常終了すると思います。後者だった場合、渡されたすべてのインスタンスを解放しないと、メモリリークとなります。 delete してよいかどうかは、関数の仕様次第だと思いました。 なお、ここで delete して異常終了しないということは、 LeetCode がこのプログラムを動かす場合は、引数として渡したメモリは delete せず、プロセスの終了時に OS に解放を任せるという実装になっているのだと思います。
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @nodchip There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
念のため確認させてください。 C++ にはポインターと参照があります。この違いについて説明してみてください。 上記の質問はひとまず置いておいて、「参照」で受け渡しをする場合、関数の外側がインスタンスの所有権を持つことがほとんどだと思います。その場合、外側で解放することになります。
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. それぞれ次のように考えています。
int a = 5;
int* p = &a; // ポインタpはa=5が格納されている番地(0x11111111みたいな)
&p = 10; // aの値は10になる
int b = 10;
int& c = b;
std::cout << c << std::endl; // 10
func(int& x) {
x += 10;
}
func(b); // bは20になるたしかにポインタと参照がごちゃごちゃになっていました。
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 実務では、ポインターの代わりに std::unique_ptr や std::shared_ptr といったスマートポインターを使用する場合があります。
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ありがとうございます。ドキュメント読んでみます。今の問題であれば、ListNodeの定義のnextをstd::unique_ptr型に置き換えて、親ノードが子ノードへのポインタの所有権を唯一持っているという形にするとよさそうです。 |
||
| } | ||
| } | ||
| } | ||
| return dummy.next; | ||
| } | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| struct ListNode { | ||
| int val; | ||
| ListNode* next; | ||
| ListNode() : val(0), next(nullptr) {} | ||
| ListNode(int x) : val(x), next(nullptr) {} | ||
| ListNode(int x, ListNode* next) : val(x), next(next) {} | ||
| }; | ||
|
|
||
| class Solution { | ||
| public: | ||
| ListNode* deleteDuplicates(ListNode* head) { | ||
| ListNode dummy(0, head); | ||
| ListNode* tail = &dummy; | ||
| while (tail->next && tail->next->next) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 好みかとは思うのですが、個人的には
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ありがとうございます!ryosukeさんのコードを参考に書いてみました。確定済みのノードと作業中のノードの意図が伝わりやすくていいですね。 class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
ListNode dummy(-1, head);
ListNode* last_non_duplicate_node = &dummy;
ListNode* cur_node = last_non_duplicate_node->next;
while (cur_node && cur_node->next) {
if (cur_node->val != cur_node->next->val) {
last_non_duplicate_node = cur_node;
cur_node = cur_node->next;
continue;
}
int val_to_remove = cur_node->val;
while (cur_node && cur_node->val == val_to_remove) {
ListNode* to_delete = cur_node;
cur_node = cur_node->next;
delete to_delete;
}
last_non_duplicate_node->next = cur_node;
}
return dummy.next;
}
}; |
||
| ListNode* cur_node = tail->next; | ||
| if (cur_node->val != cur_node->next->val) { | ||
| tail = cur_node; | ||
| continue; | ||
| } | ||
| int val_to_remove = cur_node->val; | ||
| while (cur_node && cur_node->val == val_to_remove) { | ||
| ListNode* to_delete = cur_node; | ||
| cur_node = cur_node->next; | ||
| delete to_delete; | ||
| } | ||
| tail->next = cur_node; | ||
| } | ||
| return dummy.next; | ||
| } | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| struct ListNode { | ||
| int val; | ||
| ListNode* next; | ||
| ListNode() : val(0), next(nullptr) {} | ||
| ListNode(int x) : val(x), next(nullptr) {} | ||
| ListNode(int x, ListNode* next) : val(x), next(next) {} | ||
| }; | ||
|
|
||
| class Solution { | ||
| public: | ||
| ListNode* deleteDuplicates(ListNode* head) { | ||
| ListNode dummy(-1, head); | ||
| ListNode* last_non_duplicate_node = &dummy; | ||
| ListNode* cur_node = last_non_duplicate_node->next; | ||
| while (cur_node && cur_node->next) { | ||
| if (cur_node->val != cur_node->next->val) { | ||
| last_non_duplicate_node = cur_node; | ||
| cur_node = cur_node->next; | ||
| continue; | ||
| } | ||
| int val_to_remove = cur_node->val; | ||
| while (cur_node && cur_node->val == val_to_remove) { | ||
| ListNode* to_delete = cur_node; | ||
| cur_node = cur_node->next; | ||
| delete to_delete; | ||
| } | ||
| last_non_duplicate_node->next = cur_node; | ||
| } | ||
| return dummy.next; | ||
| } | ||
| }; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 読みやすいと思います。 |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| // ポインタと参照の違い | ||
|
|
||
| #include <iostream> | ||
| void func(int& x) { | ||
| x += 10; | ||
| printf("%p\n", &x); | ||
| } | ||
| int main() { | ||
| int b = 10; | ||
| int& c = b; | ||
| std::cout << c << std::endl; // 10 | ||
| printf("%p\n", &b); | ||
| func(b); // bは20になる | ||
| std::cout << b << std::endl; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
まあ、いいと思います。ただ、cur_node の cur はあんまり読む役に立っていない気はします。
https://docs.google.com/document/d/11HV35ADPo9QxJOpJQ24FcZvtvioli770WWdZZDaLOfg/edit?tab=t.0#heading=h.xzxd7jwvkwc5
コメント集のここらへんですかね。
完走した人から気に入った人を探しておくと、毎回安定するかと思います。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ありがとうございます、私もここでcur_nodeを置くのはあまり有効でなく感じます。なかなか人が覚えられないのですが意識して見てみます。