diff --git a/src/bin/step1.rs b/src/bin/step1.rs index d640da2..a9b47fe 100644 --- a/src/bin/step1.rs +++ b/src/bin/step1.rs @@ -8,26 +8,112 @@ // 正解したら終わり /* + 問題の理解 + - 符号付き整数を値として持つソート済の単方向リンクリストが与えられる。 + 単方向リンクリストの値に着目して一意の値を持つノードのみを抽出して返す。 + 何がわからなかったか - - + - 入力をインプレイスで変更して返す方法。できそうな気はするが5分では具体的な方法まで思いつかなかった。 何を考えて解いていたか - - - - 想定ユースケース - - + - リンクリストを全走査しながら、ノードの値を配列に集める。 + 配列からcontainsのカウントが1になる値のみを抽出してソート後、リンクリストを生成して返す。 正解してから気づいたこと - - + - Rustのイテレータメソッドのcontainsではカウントを返さないことを知った。 + - 問題を見たときに思いついた解法では時間計算量がO(N^2)になっているが、O(N)にはできそうな感じはする。 + - HashMapを利用してキーにノードの値、値に出現回数を入れていき、出現回数が1の値で返却用のリンクリストを生成する方法でできる。 */ +#[derive(PartialEq, Eq, Clone, Debug)] +pub struct ListNode { + pub val: i32, + pub next: Option>, +} + +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>) -> Option> { + let mut node_values: Vec<_> = Vec::new(); + let mut target_node = head; + + while let Some(node) = target_node { + node_values.push(node.val); + target_node = node.next; + } + + let single_occurrence_values = node_values + .iter() + .filter_map(|v| { + if node_values.iter().filter(|x| v == *x).count() == 1 { + return Some(v); + } + None + }) + .collect::>(); + + single_occurrence_values + .iter() + .rev() + .fold(None, |child, v| { + let mut parent = Box::new(ListNode::new(**v)); + parent.next = child; + Some(parent) + }) + } +} #[cfg(test)] mod tests { use super::*; + fn vec_to_list_node(values: &Vec) -> Option> { + 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>) -> Vec { + 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 step1_test() {} + fn step1_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![]); + } } diff --git a/src/bin/step2.rs b/src/bin/step2.rs index e92520d..afcdf96 100644 --- a/src/bin/step2.rs +++ b/src/bin/step2.rs @@ -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がよくわからないのが原因だと考えられる。(コードが悪いということではない) + 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>, +} + +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>) -> Option> { + 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::>(); + + 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) -> Option> { + 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>) -> Vec { + 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![]); + } } diff --git a/src/bin/step2_1_in-place.rs b/src/bin/step2_1_in-place.rs new file mode 100644 index 0000000..ad967ff --- /dev/null +++ b/src/bin/step2_1_in-place.rs @@ -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 + - ループの先頭に戻る + - 重複が発生していたかをフラグから判断 + - 重複が発生していたら、カーソルの指し先の次ノードをカーソルの指し先にする + - cursor.next = node[i].next + - 重複が発生していなければ、カーソルの指し先を一つ次に進める。 + - cursor = node[i] + + - 感想 + - 他の人の実装を見ているときにtailという変数名に違和感を覚えてしまい、解法のロジックが理解できない現象に遭遇した。 + - 自分の中でtailは末尾を想起するものである。 + - 入力として与えられたリンクリストのあるノードを指してtailといっているのは、末尾ではないだろうと考えてしまう。 + - どうしても納得できなかったので、ChatGPTに相談して変数名をwrite_cursorにすることにした。 + - 学習目的でなるべく理解できるまで粘って実装してみたが、基本的には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>, +} + +impl ListNode { + #[inline] + fn new(val: i32) -> Self { + ListNode { next: None, val } + } +} + +pub struct Solution {} +impl Solution { + pub fn delete_duplicates(head: Option>) -> Option> { + 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(); + 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) -> Option> { + 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>) -> Vec { + 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![]); + } +} diff --git a/src/bin/step3.rs b/src/bin/step3.rs index dfbe3cb..9a701e9 100644 --- a/src/bin/step3.rs +++ b/src/bin/step3.rs @@ -9,23 +9,115 @@ // 作れないデータ構造があった場合は別途自作すること /* - 時間計算量: - 空間計算量: + Nは入力全体のサイズとする + 時間計算量: O(N) 全走査するため + 空間計算量: O(N) HashMapで利用する補助空間計算量 */ /* - 1回目: 分秒 - 2回目: 分秒 - 3回目: 分秒 + 1回目: 6分49秒 + 2回目: 5分22秒 + 3回目: 4分8秒 */ +/* + 問題を見たときに自然に思いついた解法を採用した。 +*/ + +use std::collections::HashMap; + +pub struct ListNode { + val: i32, + next: Option>, +} + +impl ListNode { + pub fn new(val: i32) -> Self { + Self { next: None, val } + } +} + pub struct Solution {} -impl Solution {} +impl Solution { + pub fn delete_duplicates(head: Option>) -> Option> { + 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::>(); + + 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) -> Option> { + 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>) -> Vec { + 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 step3_test() {} + fn step3_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![]); + } }