-
Notifications
You must be signed in to change notification settings - Fork 0
solve: 82.Remove Duplicates from Sorted List II #10
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 |
|---|---|---|
|
|
@@ -12,26 +12,125 @@ | |
| // 改善する時に考えたこと | ||
|
|
||
| /* | ||
| 講師陣はどのようなコメントを残すだろうか? | ||
| - | ||
|
|
||
| 他の人のコードを読んで考えたこと | ||
| - | ||
| - 様々な解法が検討されており、非常に参考になる。 | ||
| https://github.com/yas-2023/leetcode_arai60/pull/4/files | ||
|
|
||
| - LeetCodeでVimモードがあるの知らなかったので助かった。 | ||
| https://github.com/yas-2023/leetcode_arai60/pull/4/files#diff-a2c7d2692707bbe67e316042ed9e36c45f92db461cb104915bedc6638dd9aa26R41-R43 | ||
|
|
||
| 他の想定ユースケース | ||
| - | ||
| - インプレイスでの実装を理解するのに以下の実装を参考にすることにしたが、結局理解ができなかった。 | ||
| dummy,tailのうちtailがよくわからないのが原因だと考えられる。(コードが悪いということではない) | ||
|
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. 本線の最後の車両という意味です。 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. ありがとうございます。他の方のコードやレビューを確認しながら別の問題で実装に利用できたので理解できたと思います。 |
||
| https://github.com/docto-rin/leetcode/pull/4/files#diff-2bdfe1140252df4bf36c06f29251eeade51991cec8bee544c92c5b27c63cfc7aR147-R197 | ||
| インプレイスで処理する方法もstep2_1_in-place.rsで実装してみる | ||
|
|
||
| 改善する時に考えたこと | ||
| - | ||
| - step1で二重の全走査部分をHashMapを用いた方法に書き換えて処理全体の時間計算量をO(N^2) -> O(N)に改善した。 | ||
| - ナイーブな実装だが何をしているのかは分かりやすいと思う。 | ||
| */ | ||
|
|
||
| /* | ||
| Nは入力のサイズとする | ||
| 時間計算量: O(N) | ||
| 空間計算量: O(N) | ||
| */ | ||
|
|
||
| use std::collections::HashMap; | ||
|
|
||
| #[derive(PartialEq, Eq, Clone, Debug)] | ||
| pub struct ListNode { | ||
| pub val: i32, | ||
| pub next: Option<Box<ListNode>>, | ||
| } | ||
|
|
||
| impl ListNode { | ||
| #[inline] | ||
| fn new(val: i32) -> Self { | ||
| ListNode { next: None, val } | ||
| } | ||
| } | ||
|
|
||
| pub struct Solution {} | ||
| impl Solution {} | ||
| impl Solution { | ||
| pub fn delete_duplicates(head: Option<Box<ListNode>>) -> Option<Box<ListNode>> { | ||
| let mut frequency_by_node_value: HashMap<_, _> = HashMap::new(); | ||
| let mut target_node = head; | ||
|
|
||
| while let Some(node) = target_node { | ||
| frequency_by_node_value | ||
| .entry(node.val) | ||
| .and_modify(|frequency| *frequency += 1) | ||
| .or_insert(1); | ||
|
|
||
| target_node = node.next; | ||
| } | ||
|
|
||
| let mut single_occurrence_values = frequency_by_node_value | ||
| .keys() | ||
| .filter_map(|node_value| match frequency_by_node_value.get(node_value) { | ||
| Some(frequency) if *frequency == 1 => Some(node_value), | ||
| _ => None, | ||
| }) | ||
| .collect::<Vec<_>>(); | ||
|
|
||
| single_occurrence_values.sort(); | ||
|
|
||
| single_occurrence_values | ||
| .into_iter() | ||
| .rev() | ||
| .fold(None, |child, node_value| { | ||
| let mut parent = Box::new(ListNode::new(*node_value)); | ||
| parent.next = child; | ||
| Some(parent) | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use super::*; | ||
|
|
||
| fn vec_to_list_node(values: &Vec<i32>) -> Option<Box<ListNode>> { | ||
| values.into_iter().rev().fold(None, |child, v| { | ||
| let mut parent = Box::new(ListNode::new(*v)); | ||
| parent.next = child; | ||
| Some(parent) | ||
| }) | ||
| } | ||
|
|
||
| fn list_node_to_vec(head: &Option<Box<ListNode>>) -> Vec<i32> { | ||
| let mut out = Vec::new(); | ||
| let mut current_node = head; | ||
|
|
||
| while let Some(node) = current_node { | ||
| out.push(node.val); | ||
| current_node = &node.next; | ||
| } | ||
|
|
||
| out | ||
| } | ||
|
|
||
| #[test] | ||
| fn step2_test() {} | ||
| fn step2_test() { | ||
| let source_vec = vec![1, 1, 2]; | ||
| let head = vec_to_list_node(&source_vec); | ||
| assert_eq!(list_node_to_vec(&head), source_vec); | ||
| assert_eq!( | ||
| list_node_to_vec(&Solution::delete_duplicates(head)), | ||
| vec![2] | ||
| ); | ||
|
|
||
| let source_vec = vec![1, 1, 2, 3, 3]; | ||
| let head = vec_to_list_node(&source_vec); | ||
| assert_eq!(list_node_to_vec(&head), source_vec); | ||
| assert_eq!( | ||
| list_node_to_vec(&Solution::delete_duplicates(head)), | ||
| vec![2] | ||
| ); | ||
|
|
||
| let source_vec = vec![1, 1, 1]; | ||
| let head = vec_to_list_node(&source_vec); | ||
| assert_eq!(list_node_to_vec(&head), source_vec); | ||
| assert_eq!(list_node_to_vec(&Solution::delete_duplicates(head)), vec![]); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,142 @@ | ||
| // Step2_1_in-place | ||
| // 目的: インプレイスによる解法を実装しておく | ||
|
|
||
| /* | ||
| - インプレイスでの実装を理解するのに以下の実装を参考にすることにして少し手を加えた。 | ||
| https://leetcode.com/problems/remove-duplicates-from-sorted-list-ii/solutions/6940535/somehow-elegant-iterative-solution-without-unwrap-and-clone-100/ | ||
|
|
||
| - 先頭にダミーのノードを入れることで先頭から重複しているときに対応している。 | ||
| このようにダミーの要素を計算のために追加することを「番兵」と呼ぶことが分かった。 | ||
|
|
||
| - 問題の理解 | ||
| cursor = sentinel_head(先頭番兵ノード) | ||
| cursor.next = node[i](初回はhead) | ||
| node[i].next = node[i+1](初回はheadの次ノード) | ||
| -カーソルが指しているnode[i].valとnode[i+1].valが重複している間ループに入る。 | ||
| - 重複検知した時点でフラグを立てておく。 | ||
| - 次ノードの次ノードをカーソルノードの次ノードの参照先に更新する。 | ||
| - node[i].next = node[i+1].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. フラグは使わないほうが無難なので、処理を関数化できるかどうか検討してみてください。 |
||
| - 重複が発生していたら、カーソルの指し先の次ノードをカーソルの指し先にする | ||
| - cursor.next = node[i].next | ||
| - 重複が発生していなければ、カーソルの指し先を一つ次に進める。 | ||
| - cursor = node[i] | ||
|
|
||
| - 感想 | ||
| - 他の人の実装を見ているときにtailという変数名に違和感を覚えてしまい、解法のロジックが理解できない現象に遭遇した。 | ||
| - 自分の中でtailは末尾を想起するものである。 | ||
| - 入力として与えられたリンクリストのあるノードを指してtailといっているのは、末尾ではないだろうと考えてしまう。 | ||
| - どうしても納得できなかったので、ChatGPTに相談して変数名をwrite_cursorにすることにした。 | ||
|
Comment on lines
+27
to
+30
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. tailに違和感があるんですね。 in-place(というよりストリーミングといった方が正確でしょうか)に解く場合、入力のリストを線形に1回だけ舐めていき、解答に含めるかどうかをその場で判断していきます。走査の各時点において、出力することが確定したノード一覧(リスト)が1つに決まると思いますが、その末尾のことをtailと言っていますね。 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. pythonで恐縮ですが、以前似たようにtail関連のレビューをしたことがあり、よければご参考ください。 https://discordapp.com/channels/1084280443945353267/1318459221204930671/1423549410855354378 (擬似コード的なノリで眺めてもらえたらと思います。)
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.
ありがとうございます。頂いた関連レビューと上記の説明で理解できました。(別の問題の実装で使えたので理解できているはず。) |
||
| - 学習目的でなるべく理解できるまで粘って実装してみたが、基本的にはHashMapを利用した実装をすると思った。 | ||
| - アルゴリズムへの対応力が不足しているだけかも知れないが、読み書きにだいぶ時間と体力を消費している。 | ||
| - HashMapを利用した実装であれば思いついた解法をそのまま書いて空間計算量O(N) | ||
| - インプレイスでの実装では時間と考える体力をそれなりに消費して空間計算量O(1) | ||
| - 時間計算量はどちらもO(N) | ||
| - 今の自分にとっては空間計算量O(1)に改善するために時間と体力を使いすぎるので、HashMapを利用した実装を採用すると思った。 | ||
| - 選択肢としてより空間計算量が優れる実装方法を調べながら実装できること自体には大きな意義があると思った。 | ||
| - インプレイスでの実装を時間と体力をあまり使わずに実装できることがソフトウェアエンジニアとしての常識なのかは気になるところ。 | ||
| */ | ||
|
|
||
| /* | ||
| Nは入力のサイズとする | ||
| 時間計算量: O(N) | ||
| 空間計算量: O(1) | ||
| */ | ||
|
|
||
| #[derive(PartialEq, Eq, Clone, Debug)] | ||
| pub struct ListNode { | ||
| pub val: i32, | ||
| pub next: Option<Box<ListNode>>, | ||
| } | ||
|
|
||
| impl ListNode { | ||
| #[inline] | ||
| fn new(val: i32) -> Self { | ||
| ListNode { next: None, val } | ||
| } | ||
| } | ||
|
|
||
| pub struct Solution {} | ||
| impl Solution { | ||
| pub fn delete_duplicates(head: Option<Box<ListNode>>) -> Option<Box<ListNode>> { | ||
| let mut sentinel_head = Box::new(ListNode { val: 0, next: head }); | ||
| let mut write_cursor = sentinel_head.as_mut(); | ||
|
|
||
| while let Some(mut node) = write_cursor.next.take() { | ||
| let mut has_duplicate = false; | ||
|
|
||
| while let Some(next_node) = node.next.as_mut() { | ||
| // 重複していないのでループを抜ける | ||
| if node.val != next_node.val { | ||
| break; | ||
| } | ||
|
|
||
| has_duplicate = true; | ||
| // 次のノードと重複しているので飛ばして、次の次のノードを次のノードにする。 | ||
| node.next = next_node.next.take(); | ||
|
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.
|
||
| continue; | ||
| } | ||
|
|
||
| if has_duplicate { | ||
| // 現在のノードは重複している値を持つので飛ばして次のノードと入れ替える。 | ||
| write_cursor.next = node.next; | ||
| continue; | ||
| } | ||
|
|
||
| // 重複していないのでwrite_cursorを次のノードに移動する。 | ||
| write_cursor = write_cursor.next.insert(node); | ||
| } | ||
|
|
||
| sentinel_head.next | ||
| } | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use super::*; | ||
|
|
||
| fn vec_to_list_node(values: &Vec<i32>) -> Option<Box<ListNode>> { | ||
| values.into_iter().rev().fold(None, |child, v| { | ||
| let mut parent = Box::new(ListNode::new(*v)); | ||
| parent.next = child; | ||
| Some(parent) | ||
| }) | ||
| } | ||
|
|
||
| fn list_node_to_vec(head: &Option<Box<ListNode>>) -> Vec<i32> { | ||
| let mut out = Vec::new(); | ||
| let mut current_node = head; | ||
|
|
||
| while let Some(node) = current_node { | ||
| out.push(node.val); | ||
| current_node = &node.next; | ||
| } | ||
|
|
||
| out | ||
| } | ||
|
|
||
| #[test] | ||
| fn step2_1_test() { | ||
| let source_vec = vec![1, 1, 2]; | ||
| let head = vec_to_list_node(&source_vec); | ||
| assert_eq!(list_node_to_vec(&head), source_vec); | ||
| assert_eq!( | ||
| list_node_to_vec(&Solution::delete_duplicates(head)), | ||
| vec![2] | ||
| ); | ||
|
|
||
| let source_vec = vec![1, 1, 2, 3, 3]; | ||
| let head = vec_to_list_node(&source_vec); | ||
| assert_eq!(list_node_to_vec(&head), source_vec); | ||
| assert_eq!( | ||
| list_node_to_vec(&Solution::delete_duplicates(head)), | ||
| vec![2] | ||
| ); | ||
|
|
||
| let source_vec = vec![1, 1, 1]; | ||
| let head = vec_to_list_node(&source_vec); | ||
| assert_eq!(list_node_to_vec(&head), source_vec); | ||
| assert_eq!(list_node_to_vec(&Solution::delete_duplicates(head)), vec![]); | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.
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.
この方法はリストがソート済みのときにしか使えないので、ソートされてないときも使える方法を考えてみてもいいかもしれません。inplaceでつなぎかえる方法もソート済み前提ですね。忘れてください。