diff --git a/src/bin/step1.rs b/src/bin/step1.rs index d640da2..67509b3 100644 --- a/src/bin/step1.rs +++ b/src/bin/step1.rs @@ -8,26 +8,59 @@ // 正解したら終わり /* + 問題の概要: 2つの整数を持つ配列が与えられる。2つのどちらにも含まれる整数列を戻り値として返す。 + 例: + input: [1,2,2,1],[2,2], output: [2] + input: [4,9,5],[9,4,9,8,4], output: [4,9]又は[9,4] + 何がわからなかったか - - + - 特になし 何を考えて解いていたか - - - - 想定ユースケース - - + - 愚直に配列を全て見ていって自身と同じ値がもう片方の配列にあれば結果の配列に追加して最後に返す。 + 配列を全て見るので時間計算量: O(N^2),空間計算量: O(N) + - HashSetに片方の配列の値を追加する。もう片方の配列を全走査しながらHashSetに値が含まれているか探索して、含まれていれば + 結果の配列に追加していく。 + - HashSetのメソッドを確認していたらそのままのintersectionメソッドを見つけてしまい、 + アルゴリズムについて考える必要がなくなってしまった。とりあえずHashSet::intersection()メソッドを利用しない形で書く。 正解してから気づいたこと - - + - Vec型であるnums2.contains()は線形探索になるのでO(N)になるが、HashSetであれば探索に平均O(1)となるのでHashSetにしたほうが良さそう。 + 入力の型がi32で固定長なのでHashSetにするときに伴うハッシュ化はO(N)になるがループの外で一度だけ行うので問題ない。 + - ループで値を取り出す配列はサイズが小さい方にしたほうが効率が良い。内側のHashSetの探索時間はO(1)とすると計算量Nが支配的になる。 + Nは配列のサイズなので全走査する配列はより小さい方が時間計算量が少なくなる。 + - 変数名intersectionがメソッド名と被っていて違和感を感じた。 + */ +use std::collections::HashSet; + pub struct Solution {} -impl Solution {} +impl Solution { + pub fn intersection(nums1: Vec, nums2: Vec) -> Vec { + let mut intersection = HashSet::with_capacity(nums1.len().min(nums2.len())); + for num1 in nums1 { + if nums2.contains(&num1) { + intersection.insert(num1); + } + } + + intersection.into_iter().collect() + } +} #[cfg(test)] mod tests { use super::*; #[test] - fn step1_test() {} + fn step1_test() { + let mut result = Solution::intersection(vec![1, 2, 2, 1], vec![2, 2]); + result.sort(); + assert_eq!(result, vec![2]); + + let mut result = Solution::intersection(vec![4, 9, 5], vec![9, 4, 9, 8, 4]); + result.sort(); + assert_eq!(result, vec![4, 9]); + } } diff --git a/src/bin/step2.rs b/src/bin/step2.rs index e92520d..bc50eec 100644 --- a/src/bin/step2.rs +++ b/src/bin/step2.rs @@ -13,25 +13,64 @@ /* 講師陣はどのようなコメントを残すだろうか? - - + - if文周りの書き方が冗長なので、もう少しすっきり書ける方法がある 他の人のコードを読んで考えたこと - - + - HashSetを利用したワンライナーの例。mapで参照を返すだけの部分は書き直せそう。 + https://leetcode.com/problems/intersection-of-two-arrays/solutions/4406232/one-liner-using-set-operations/ - 他の想定ユースケース - - + - なぜbinary_searchを使っているのか、HashSet自体は使っているのに、filterでの重複判定に使っている理由などよくわからなかった。 + Runtime Beats 100%であることをタイトルで示しているが、step1.rsの自身の実装でもRuntime Beats 100%となるので、この解法は選択肢に入らないなと思った。 + https://leetcode.com/problems/intersection-of-two-arrays/solutions/5678828/beats-100-in-runtime/ 改善する時に考えたこと - - + - メソッド名と同名の変数名とならないようintersectedに変更 + - nums1とnums2でサイズが小さい方を全走査に利用する。もう片方はHashSetに変換する。 + Vec.contains() -> HashSet.contains()に変更することで時間計算量を改善する。 + - そもそもRustではHashSet::intersection()メソッドが利用できるので、step3.rsではこの方法で書く。 */ +use std::collections::HashSet; + pub struct Solution {} -impl Solution {} +impl Solution { + pub fn intersection(nums1: Vec, nums2: Vec) -> Vec { + let linear_search_base; + let others: HashSet; + let mut intersected: HashSet = HashSet::with_capacity(nums1.len().min(nums2.len())); + + // 入力のデータ型がi32と固定長なので、HashSet作成時の時間計算量はO(1) + if nums1.len() <= nums2.len() { + linear_search_base = nums1; + others = HashSet::from_iter(nums2); + } else { + linear_search_base = nums2; + others = HashSet::from_iter(nums1); + } + + // ここで全走査するので、サイズが小さい方が効率が良い + for base in linear_search_base.into_iter() { + if others.contains(&base) { + intersected.insert(base); + } + } + + intersected.into_iter().collect() + } +} #[cfg(test)] mod tests { use super::*; #[test] - fn step2_test() {} + fn step2_test() { + let mut result = Solution::intersection(vec![1, 2, 2, 1], vec![2, 2]); + result.sort(); + assert_eq!(result, vec![2]); + + let mut result = Solution::intersection(vec![4, 9, 5], vec![9, 4, 9, 8, 4]); + result.sort(); + assert_eq!(result, vec![4, 9]); + } } diff --git a/src/bin/step2_1.rs b/src/bin/step2_1.rs new file mode 100644 index 0000000..1da823a --- /dev/null +++ b/src/bin/step2_1.rs @@ -0,0 +1,50 @@ +// Step2_1 +// 目的: step2で違和感を感じたif文の部分を修正する + +/* + 他の人のコードを読んで考えたこと + - 標準ライブラリのHashSet::intersection()メソッド(実装はhashbrownクレート)の実装を確認していたところ、 + if文でよりスッキリかける書き方を見つけた。 + https://docs.rs/hashbrown/latest/src/hashbrown/set.rs.html#799-809 + + 改善する時に考えたこと + - step2で書いたif分の部分が冗長で違和感を感じたので、より簡潔な形になるように書きなおす。 +*/ + +use std::collections::HashSet; + +pub struct Solution {} +impl Solution { + pub fn intersection(nums1: Vec, nums2: Vec) -> Vec { + let mut intersected = HashSet::<_>::with_capacity(nums1.len().min(nums2.len())); + let (smaller_nums, larger_nums) = if nums1.len() <= nums2.len() { + (nums1, HashSet::<_>::from_iter(nums2)) + } else { + (nums2, HashSet::<_>::from_iter(nums1)) + }; + + for small in smaller_nums { + if larger_nums.contains(&small) { + intersected.insert(small); + } + } + + intersected.into_iter().collect() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn step2_1_test() { + let mut result = Solution::intersection(vec![1, 2, 2, 1], vec![2, 2]); + result.sort(); + assert_eq!(result, vec![2]); + + let mut result = Solution::intersection(vec![4, 9, 5], vec![9, 4, 9, 8, 4]); + result.sort(); + assert_eq!(result, vec![4, 9]); + } +} diff --git a/src/bin/step3.rs b/src/bin/step3.rs index a8f053c..55bfdb5 100644 --- a/src/bin/step3.rs +++ b/src/bin/step3.rs @@ -8,17 +8,43 @@ // レビューを受ける // 作れないデータ構造があった場合は別途自作すること +use std::collections::HashSet; + +/* + nums1のサイズをN,nums2のサイズをMとする + 時間計算量: O(N + M) HashSetの初期化(from_iter())でO(1)の操作をN回,M回行うため。 + 空間計算量: O(N + M) num1,num2のHashSetが同時に存在するため。 +*/ + /* - 時間計算量: - 空間計算量: + memo: + ワンライナーで書くとnums1とnums2を両方HashSetにする必要があるので、空間計算量が増える。 + step2_1.rsの実装だと入力のうち、どちらか一方だけをHashSetにして、もう一方はそのまま(in-place)操作するので、 + 空間計算量が優れている。 */ + pub struct Solution {} -impl Solution {} +impl Solution { + pub fn intersection(nums1: Vec, nums2: Vec) -> Vec { + HashSet::<_>::from_iter(nums1) + .intersection(&HashSet::from_iter(nums2)) + .copied() + .collect() + } +} #[cfg(test)] mod tests { use super::*; #[test] - fn step3_test() {} + fn step3_test() { + let mut result = Solution::intersection(vec![1, 2, 2, 1], vec![2, 2]); + result.sort(); + assert_eq!(result, vec![2]); + + let mut result = Solution::intersection(vec![4, 9, 5], vec![9, 4, 9, 8, 4]); + result.sort(); + assert_eq!(result, vec![4, 9]); + } }