diff --git a/Cargo.lock b/Cargo.lock index 9a49b34..af39369 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5,3 +5,61 @@ version = 4 [[package]] name = "leetcode-practice" version = "0.1.0" +dependencies = [ + "thiserror", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" diff --git a/Cargo.toml b/Cargo.toml index 55c2bea..73359fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,4 +1,7 @@ [package] name= "leetcode-practice" version = "0.1.0" -edition = "2021" \ No newline at end of file +edition = "2021" + +[dependencies] +thiserror = "2.0.18" diff --git a/src/bin/step1.rs b/src/bin/step1.rs new file mode 100644 index 0000000..c515475 --- /dev/null +++ b/src/bin/step1.rs @@ -0,0 +1,213 @@ +// Step1 +// 目的: 方法を思いつく + +// 方法 +// 5分考えてわからなかったら答えをみる +// 答えを見て理解したと思ったら全部消して答えを隠して書く +// 5分筆が止まったらもう一回みて全部消す +// 正解したら終わり + +/* + 問題の理解 + - (start,end)の情報を持つ配列intervalsが与えられる。intervalsの要素で重複する区間の有無をbooleanで返す。 + start=ミーティング開始時間、end=ミーティング終了時間を表している。intervalsはミーティング時間を表す区間を複数持っており、全てのミーティング時間で重複の有無を確認したいと理解した。 + [(0,8),(8,10)]は重複していない。 + + 何を考えて解いていたか + - 採点システムがRustに対応していないのでテストケース例の入出力からテストコードを実装する。実装できたと思ったらGPT-5.2にテストケースを追加してもらってテストが通るかでAcceptedかを判定する。 + - (start,end)はleft-close,right-openな半開区間として見るのが自然そうだと思った。[(0,8), (8,10)]は重複していないので。 + - すぐに解法が思いつかないので、図に書いて考えてみる。 + - 任意の区間を基準(intervals[0])として考えて、bs(基準スタート), be(基準エンド)としたときに、bs <= e && be <= s を満たすかどうか確認していき、falseを見つけたら早期リターンで良さそう。 + - intervals[0], intervals[1]の区間が重複していなくても、intervals[1], intervals[2]が重複しているケースも見ないといけないので全ての組み合わせを確認する必要がある。 + - [(1,2), (2,4), (2,3)] では(2,4), (2,3)が重複している + - 2重ループで外側 0..intervals.len() 内側 i+1..intervals.len()として全ての組み合わせを見ながら重複区間を見つけたら早期リターンで解けそう。 + 入力の制約から時間計算量は(10 ^ 4) ^ 2 / 10 ^ 8 = 1秒 となり、遅いが現実的ではあると考える。 + 自分で考えたテストケースは通ったので、GPT-5.2に生成させたテストケースを通るか確認する。 + unsorted but non-overlappingのテストケースが通らなかった。 + base_interval.start < interval.startであることが前提になっていることに気付いていなかった。この条件を満たすようにswapする処理を追加して全てのテストケースをパスした。 + 改善できるかコードを確認していたところ、intervals.startで一度ソートしてしまえば、時間計算量がO(n log n)で済むことに気付いた。 + ソートしてしまえば、intervals[i] , intervals[i+1]の区間が重複していないかを確認するだけで良いため。 + step1a.rsで試してみる。 + + 何がわからなかったか + - ナイーブではない実装が思いつかなかった。step2で他の人のコードを読む。 + + 正解してから気づいたこと + - 最初に一度ソートすることで、時間計算量を改善できると思った。step1a.rsで試す。 +*/ + +use thiserror::Error; + +#[derive(Error, Debug, PartialEq)] +pub enum IntervalError { + #[error("invalid interval. end must be greater start. start: {0}, end: {1}")] + InvalidInterval(u32, u32), +} + +#[derive(Debug)] +pub struct Interval { + start: u32, + end: u32, +} +impl Interval { + pub fn new(start: u32, end: u32) -> Result { + if end <= start { + return Err(IntervalError::InvalidInterval(start, end)); + } + + Ok(Self { start, end }) + } +} + +pub struct Solution {} +impl Solution { + pub fn can_attend_meetings(intervals: Vec) -> bool { + for i in 0..intervals.len() { + for j in i + 1..intervals.len() { + let mut base_interval = &intervals[i]; + let mut interval = &intervals[j]; + + if interval.start < base_interval.start { + std::mem::swap(&mut base_interval, &mut interval); + } + + if base_interval.start <= interval.end && base_interval.end <= interval.start { + continue; + } + + return false; + } + } + + true + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn to_interval(interval: (u32, u32)) -> Interval { + Interval::new(interval.0, interval.1).unwrap() + } + + #[test] + fn step1_test() { + let intervals = vec![(0, 30), (5, 10), (15, 20)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::can_attend_meetings(intervals), false); + + let intervals = vec![(5, 8), (9, 15)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::can_attend_meetings(intervals), true); + + let intervals = vec![(5, 8), (8, 9)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::can_attend_meetings(intervals), true); + + let intervals = vec![(1, 2), (2, 4), (2, 3)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::can_attend_meetings(intervals), false); + } + + #[test] + fn invalid_interval_test() { + let (start, end) = (1, 0); + let error = Interval::new(start, end).unwrap_err(); + assert_eq!(error, IntervalError::InvalidInterval(start, end)); + + let (start, end) = (1, 1); + let error = Interval::new(start, end).unwrap_err(); + assert_eq!(error, IntervalError::InvalidInterval(start, end)); + } + + // GPT-5.2によって生成 + #[test] + fn additional_cases_test() { + // empty: meetings無しなら参加可能(仕様として自然) + let intervals = vec![].into_iter().map(to_interval).collect::>(); + assert_eq!(Solution::can_attend_meetings(intervals), true); + + // single: 1件なら参加可能 + let intervals = vec![(1, 2)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::can_attend_meetings(intervals), true); + + // unsorted but non-overlapping: ソートして判定できるか + let intervals = vec![(10, 12), (1, 3), (4, 9)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::can_attend_meetings(intervals), true); + + // unsorted with overlap + let intervals = vec![(10, 12), (1, 5), (4, 9)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::can_attend_meetings(intervals), false); + + // fully contained: 内包((2,3) が (1,10) に含まれる) + let intervals = vec![(1, 10), (2, 3)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::can_attend_meetings(intervals), false); + + // same start: 開始時刻が同一なら必ず重なる(endが異なる) + let intervals = vec![(5, 7), (5, 6)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::can_attend_meetings(intervals), false); + + // same interval duplicated + let intervals = vec![(1, 2), (1, 2)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::can_attend_meetings(intervals), false); + + // touching chain: 端点で接するのはOK(半開区間想定) + let intervals = vec![(0, 1), (1, 2), (2, 3), (3, 10)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::can_attend_meetings(intervals), true); + + // overlap by 1: 1だけ重なる + let intervals = vec![(0, 2), (1, 3)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::can_attend_meetings(intervals), false); + + // large numbers + let intervals = vec![ + (0, 1), + (1_000_000_000, 1_000_000_100), + (1_000_000_100, 2_000_000_000), + ] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::can_attend_meetings(intervals), true); + + // long range conflicts with later small range + let intervals = vec![(0, 100), (50, 60), (100, 120)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::can_attend_meetings(intervals), false); + } +} diff --git a/src/bin/step1a.rs b/src/bin/step1a.rs new file mode 100644 index 0000000..6193027 --- /dev/null +++ b/src/bin/step1a.rs @@ -0,0 +1,188 @@ +// Step1a +// 目的: step1のコードから思いついた改善を試す。 + +/* + 改善するときに考えていたこと + - intervalsを可変参照では受け取らないことにした。 + - ミーティング時間に重複が無いかを確認したいだけなのに、可変参照で受け取るとミーティング時間などの中身まで変更されるかもしれないという不安を呼び出し側に与えそうなのでやめた。 + + n = intervals.len() + 時間計算量: O(n log n) ソートが支配的 + 空間計算量: O(1) +*/ + +use thiserror::Error; + +#[derive(Error, Debug, PartialEq)] +pub enum IntervalError { + #[error("invalid interval. end must be greater start. start: {0}, end: {1}")] + InvalidInterval(u32, u32), +} + +#[derive(Debug)] +pub struct Interval { + start: u32, + end: u32, +} +impl Interval { + pub fn new(start: u32, end: u32) -> Result { + if end <= start { + return Err(IntervalError::InvalidInterval(start, end)); + } + + Ok(Self { start, end }) + } +} + +pub struct Solution {} +impl Solution { + pub fn can_attend_meetings(mut intervals: Vec) -> bool { + // intervalsは呼び出し側からmoveされており、呼び出し側に影響を与えない。 + // 呼び出し側はintervals.clone()するか、そのままmoveするかを選べる。 + intervals.sort_by(|a, b| a.start.cmp(&b.start)); + + for i in 0..intervals.len() { + let Some(next_interval) = intervals.get(i + 1) else { + break; + }; + let interval = &intervals[i]; + + if interval.start <= next_interval.end && interval.end <= next_interval.start { + continue; + } + + return false; + } + + true + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn to_interval(interval: (u32, u32)) -> Interval { + Interval::new(interval.0, interval.1).unwrap() + } + + #[test] + fn step1a_test() { + let intervals = vec![(0, 30), (5, 10), (15, 20)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::can_attend_meetings(intervals), false); + + let intervals = vec![(5, 8), (9, 15)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::can_attend_meetings(intervals), true); + + let intervals = vec![(5, 8), (8, 9)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::can_attend_meetings(intervals), true); + + let intervals = vec![(1, 2), (2, 4), (2, 3)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::can_attend_meetings(intervals), false); + } + + #[test] + fn invalid_interval_test() { + let (start, end) = (1, 0); + let error = Interval::new(start, end).unwrap_err(); + assert_eq!(error, IntervalError::InvalidInterval(start, end)); + + let (start, end) = (1, 1); + let error = Interval::new(start, end).unwrap_err(); + assert_eq!(error, IntervalError::InvalidInterval(start, end)); + } + + // GPT-5.2によって生成 + #[test] + fn additional_cases_test() { + // empty: meetings無しなら参加可能(仕様として自然) + let intervals = vec![].into_iter().map(to_interval).collect::>(); + assert_eq!(Solution::can_attend_meetings(intervals), true); + + // single: 1件なら参加可能 + let intervals = vec![(1, 2)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::can_attend_meetings(intervals), true); + + // unsorted but non-overlapping: ソートして判定できるか + let intervals = vec![(10, 12), (1, 3), (4, 9)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::can_attend_meetings(intervals), true); + + // unsorted with overlap + let intervals = vec![(10, 12), (1, 5), (4, 9)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::can_attend_meetings(intervals), false); + + // fully contained: 内包((2,3) が (1,10) に含まれる) + let intervals = vec![(1, 10), (2, 3)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::can_attend_meetings(intervals), false); + + // same start: 開始時刻が同一なら必ず重なる(endが異なる) + let intervals = vec![(5, 7), (5, 6)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::can_attend_meetings(intervals), false); + + // same interval duplicated + let intervals = vec![(1, 2), (1, 2)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::can_attend_meetings(intervals), false); + + // touching chain: 端点で接するのはOK(半開区間想定) + let intervals = vec![(0, 1), (1, 2), (2, 3), (3, 10)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::can_attend_meetings(intervals), true); + + // overlap by 1: 1だけ重なる + let intervals = vec![(0, 2), (1, 3)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::can_attend_meetings(intervals), false); + + // large numbers + let intervals = vec![ + (0, 1), + (1_000_000_000, 1_000_000_100), + (1_000_000_100, 2_000_000_000), + ] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::can_attend_meetings(intervals), true); + + // long range conflicts with later small range + let intervals = vec![(0, 100), (50, 60), (100, 120)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::can_attend_meetings(intervals), false); + } +} diff --git a/src/bin/step2.rs b/src/bin/step2.rs new file mode 100644 index 0000000..68d4c48 --- /dev/null +++ b/src/bin/step2.rs @@ -0,0 +1,216 @@ +// Step2 +// 目的: 自然な書き方を考えて整理する + +// 方法 +// Step1のコードを読みやすくしてみる +// 他の人のコードを2つは読んでみること +// 正解したら終わり + +// 以下をメモに残すこと +// 講師陣はどのようなコメントを残すだろうか? +// 他の人のコードを読んで考えたこと +// 改善する時に考えたこと + +/* + 他の人のコードを読んで考えたこと + https://github.com/skypenguins/coding-practice/pull/25 + - NeetCodeに問題があることを知った。step1.rs,step1a.rsともにAcceptedになることが確認できた。 + https://neetcode.io/problems/meeting-schedule/question + + https://github.com/tokuhirat/LeetCode/pull/55/changes#diff-b5d45bde6718c036bbfcde726bed8503a608ff8f9d0836c44647b97fbb5d9fb0R4 + > 終了時刻が早い順に見ていき、それより早く開始する会議があれば参加できない。 + - 問題を自分とは異なる始点から捉えているのが面白いと思った。アルゴリズムを考える時に、自然言語で別の言い方にすると(問題設定をより洗練する?)コードがよりシンプルになるという例に見える。 + + https://github.com/tokuhirat/LeetCode/pull/55/changes#r2286546596 + - 自分は変数の主役を考えるのが面倒なので、数直線上の並びになるように < を使うのが好きなので好みの部分だなと思った。ただし、この方針で否定形を使わないと行けない場合は否定形を使わなくて済む記述を優先するなと思った。 + + https://github.com/olsen-blue/Arai60/pull/56/changes#diff-ab6cfa3e835a34ed5eb3a6822101cce7a548f12d24be2a329849b326fdc792b6R7 + - 累積和は思いつかなかったので、他の人の書いたコードを読む練習に良さそう。 + + https://github.com/Yoshiki-Iwasa/Arai60/pull/60/changes#diff-7e5d8cbda3fd172d8f65764fe82e6ce4fe0ddef03db2dd8e8569cc620967d8b0R30 + - sort_by_keyメソッドで必要十分でシンプルなのでこちらのほうが良いと思った。 + + https://github.com/Yoshiki-Iwasa/Arai60/pull/60/changes#diff-62432b9fe4023e71399f571d3ac42125fd73cfbeded458f8b97d12fc5215016fR23 + - 自分のstep1a.rsのコードはこのコードとやっていることは同じだなと思った。all()メソッドについてもfalseを見つけたら早期リターンしてくれる。 + https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.all + > all() is short-circuiting; in other words, it will stop processing as soon as it finds a false, given that no matter what else happens, the result will also be false. + + https://github.com/hayashi-ay/leetcode/pull/59/changes#diff-ab6cfa3e835a34ed5eb3a6822101cce7a548f12d24be2a329849b326fdc792b6R21 + - Pythonのドキュメントには結構ラフなことも書いてあって面白いと思った。 + + https://github.com/hayashi-ay/leetcode/pull/59/changes#diff-ab6cfa3e835a34ed5eb3a6822101cce7a548f12d24be2a329849b326fdc792b6R24 + - heapを使った解法は思いつかなかった。ソートしたいという目的には一致していると思った。ある時点のスケジュールから重複が存在するかを確認する用途ではソートの方が良いと思った。 + ScheduleManagerのような構造体でschedulesを状態として扱うような場合はBinaryHeapが適していそうだと思った。 + + 改善する時に考えたこと + - ソート済みであることを考えると、if interval.start <= next_interval.end && interval.end <= next_interval.start は素直だが冗長でもっと短く書ける。 + 肯定形であれば、if interval.end <= next_interval.start then true + - sort_by_key(),window(),all()メソッドを活用してスッキリと書ける。 + + 所感 + - 最初 w.first().unwrap(),w.last().unwrap()と書いていたが、どっちにしろunwrap()でエラー処理しないなら結果は変わらないので&w[0],&w[1]に書き換えた。 + windowsメソッドの引数から範囲外アクセスにならないのは自明であるため。 +*/ + +use thiserror::Error; + +#[derive(Error, Debug, PartialEq)] +pub enum IntervalError { + #[error("invalid interval. end must be greater start. start: {0}, end: {1}")] + InvalidInterval(u32, u32), +} + +#[derive(Debug)] +pub struct Interval { + start: u32, + end: u32, +} +impl Interval { + pub fn new(start: u32, end: u32) -> Result { + if end <= start { + return Err(IntervalError::InvalidInterval(start, end)); + } + + Ok(Self { start, end }) + } +} + +pub struct Solution {} +impl Solution { + pub fn can_attend_meetings(mut intervals: Vec) -> bool { + intervals.sort_by_key(|v| v.start); + intervals.windows(2).all(|w| { + let (interval, next_interval) = (&w[0], &w[1]); + interval.end <= next_interval.start + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn to_interval(interval: (u32, u32)) -> Interval { + Interval::new(interval.0, interval.1).unwrap() + } + + #[test] + fn step2_test() { + let intervals = vec![(0, 30), (5, 10), (15, 20)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::can_attend_meetings(intervals), false); + + let intervals = vec![(5, 8), (9, 15)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::can_attend_meetings(intervals), true); + + let intervals = vec![(5, 8), (8, 9)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::can_attend_meetings(intervals), true); + + let intervals = vec![(1, 2), (2, 4), (2, 3)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::can_attend_meetings(intervals), false); + } + + #[test] + fn invalid_interval_test() { + let (start, end) = (1, 0); + let error = Interval::new(start, end).unwrap_err(); + assert_eq!(error, IntervalError::InvalidInterval(start, end)); + + let (start, end) = (1, 1); + let error = Interval::new(start, end).unwrap_err(); + assert_eq!(error, IntervalError::InvalidInterval(start, end)); + } + + // GPT-5.2によって生成 + #[test] + fn additional_cases_test() { + // empty: meetings無しなら参加可能(仕様として自然) + let intervals = vec![].into_iter().map(to_interval).collect::>(); + assert_eq!(Solution::can_attend_meetings(intervals), true); + + // single: 1件なら参加可能 + let intervals = vec![(1, 2)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::can_attend_meetings(intervals), true); + + // unsorted but non-overlapping: ソートして判定できるか + let intervals = vec![(10, 12), (1, 3), (4, 9)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::can_attend_meetings(intervals), true); + + // unsorted with overlap + let intervals = vec![(10, 12), (1, 5), (4, 9)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::can_attend_meetings(intervals), false); + + // fully contained: 内包((2,3) が (1,10) に含まれる) + let intervals = vec![(1, 10), (2, 3)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::can_attend_meetings(intervals), false); + + // same start: 開始時刻が同一なら必ず重なる(endが異なる) + let intervals = vec![(5, 7), (5, 6)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::can_attend_meetings(intervals), false); + + // same interval duplicated + let intervals = vec![(1, 2), (1, 2)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::can_attend_meetings(intervals), false); + + // touching chain: 端点で接するのはOK(半開区間想定) + let intervals = vec![(0, 1), (1, 2), (2, 3), (3, 10)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::can_attend_meetings(intervals), true); + + // overlap by 1: 1だけ重なる + let intervals = vec![(0, 2), (1, 3)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::can_attend_meetings(intervals), false); + + // large numbers + let intervals = vec![ + (0, 1), + (1_000_000_000, 1_000_000_100), + (1_000_000_100, 2_000_000_000), + ] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::can_attend_meetings(intervals), true); + + // long range conflicts with later small range + let intervals = vec![(0, 100), (50, 60), (100, 120)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::can_attend_meetings(intervals), false); + } +} diff --git a/src/bin/step2a.rs b/src/bin/step2a.rs new file mode 100644 index 0000000..fcb6524 --- /dev/null +++ b/src/bin/step2a.rs @@ -0,0 +1,210 @@ +// Step2a +// 目的: 他の人が書いたコードを写経して理解する練習 + +// 方法 +// Step1のコードを読みやすくしてみる +// 他の人のコードを2つは読んでみること +// 正解したら終わり + +// 以下をメモに残すこと +// 講師陣はどのようなコメントを残すだろうか? +// 他の人のコードを読んで考えたこと +// 改善する時に考えたこと + +/* + https://github.com/olsen-blue/Arai60/pull/56/changes#diff-ab6cfa3e835a34ed5eb3a6822101cce7a548f12d24be2a329849b326fdc792b6R7 + - 累積和の解法。 + + 解法の理解 + - ミーティングが始まったら+1してミーティングが終了したら-1する + - ミーティングが始まって(+1)次のミーティングが始まる(+1)とprefix_sumが2になるので重複していることが分かる。 + - ミーティングが始まる(+1),ミーティングが終わる(-1)のように重複がないとprefix_sumは常に1以下となり、重複していないことが分かる。 + + 考えていたこと + - この解法であればintervalsをソートしないので、引数は参照で受け取れる。累積和の配列のメモリを追加で確保する必要があるというトレードオフはある。 + - 参考にしたコードでは累積和の配列サイズを初期化する部分が問題の制約に依存している(問題の制約に基づいて配列上限サイズを決め打ちしている)ので、ヒープを使ったほうがより良い気がする。 + + 所感 + - 最初見た時に理解するのに苦労した。 + - この問題文を見た時に最初に累積和が思いつくという点に驚いた。優劣ではなくて自分の視点の外側にあるという感想。 +*/ + +use std::{cmp::Reverse, collections::BinaryHeap}; + +use thiserror::Error; + +#[derive(Error, Debug, PartialEq)] +pub enum IntervalError { + #[error("invalid interval. end must be greater start. start: {0}, end: {1}")] + InvalidInterval(u32, u32), +} + +#[derive(Debug)] +pub struct Interval { + start: u32, + end: u32, +} +impl Interval { + pub fn new(start: u32, end: u32) -> Result { + if end <= start { + return Err(IntervalError::InvalidInterval(start, end)); + } + + Ok(Self { start, end }) + } +} + +pub struct Solution {} +impl Solution { + /* + 関数のシグネチャがNeetCode採点システムに適合しません。 + */ + pub fn can_attend_meetings(intervals: &[Interval]) -> bool { + let mut time_to_use_room_key_count = BinaryHeap::new(); + for interval in intervals { + time_to_use_room_key_count.push(Reverse((interval.start, 1))); + time_to_use_room_key_count.push(Reverse((interval.end, -1))); + } + + let mut using_room_key_count = 0; + while let Some(Reverse((_, room_key_count))) = time_to_use_room_key_count.pop() { + using_room_key_count += room_key_count; + + let is_conflict_use_key = 2 <= using_room_key_count; + if is_conflict_use_key { + return false; + } + } + + true + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn to_interval(interval: (u32, u32)) -> Interval { + Interval::new(interval.0, interval.1).unwrap() + } + + #[test] + fn step2a_test() { + let intervals = vec![(0, 30), (5, 10), (15, 20)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::can_attend_meetings(&intervals), false); + + let intervals = vec![(5, 8), (9, 15)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::can_attend_meetings(&intervals), true); + + let intervals = vec![(5, 8), (8, 9)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::can_attend_meetings(&intervals), true); + + let intervals = vec![(1, 2), (2, 4), (2, 3)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::can_attend_meetings(&intervals), false); + } + + #[test] + fn invalid_interval_test() { + let (start, end) = (1, 0); + let error = Interval::new(start, end).unwrap_err(); + assert_eq!(error, IntervalError::InvalidInterval(start, end)); + + let (start, end) = (1, 1); + let error = Interval::new(start, end).unwrap_err(); + assert_eq!(error, IntervalError::InvalidInterval(start, end)); + } + + // GPT-5.2によって生成 + #[test] + fn additional_cases_test() { + // empty: meetings無しなら参加可能(仕様として自然) + let intervals = vec![].into_iter().map(to_interval).collect::>(); + assert_eq!(Solution::can_attend_meetings(&intervals), true); + + // single: 1件なら参加可能 + let intervals = vec![(1, 2)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::can_attend_meetings(&intervals), true); + + // unsorted but non-overlapping: ソートして判定できるか + let intervals = vec![(10, 12), (1, 3), (4, 9)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::can_attend_meetings(&intervals), true); + + // unsorted with overlap + let intervals = vec![(10, 12), (1, 5), (4, 9)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::can_attend_meetings(&intervals), false); + + // fully contained: 内包((2,3) が (1,10) に含まれる) + let intervals = vec![(1, 10), (2, 3)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::can_attend_meetings(&intervals), false); + + // same start: 開始時刻が同一なら必ず重なる(endが異なる) + let intervals = vec![(5, 7), (5, 6)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::can_attend_meetings(&intervals), false); + + // same interval duplicated + let intervals = vec![(1, 2), (1, 2)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::can_attend_meetings(&intervals), false); + + // touching chain: 端点で接するのはOK(半開区間想定) + let intervals = vec![(0, 1), (1, 2), (2, 3), (3, 10)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::can_attend_meetings(&intervals), true); + + // overlap by 1: 1だけ重なる + let intervals = vec![(0, 2), (1, 3)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::can_attend_meetings(&intervals), false); + + // large numbers + let intervals = vec![ + (0, 1), + (1_000_000_000, 1_000_000_100), + (1_000_000_100, 2_000_000_000), + ] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::can_attend_meetings(&intervals), true); + + // long range conflicts with later small range + let intervals = vec![(0, 100), (50, 60), (100, 120)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::can_attend_meetings(&intervals), false); + } +} diff --git a/src/bin/step3.rs b/src/bin/step3.rs new file mode 100644 index 0000000..aac5192 --- /dev/null +++ b/src/bin/step3.rs @@ -0,0 +1,191 @@ +// Step3 +// 目的: 覚えられないのは、なんか素直じゃないはずなので、そこを探し、ゴールに到達する + +// 方法 +// 時間を測りながらもう一度解く +// 10分以内に一度もエラーを吐かず正解 +// これを3回連続でできたら終わり +// レビューを受ける +// 作れないデータ構造があった場合は別途自作すること + +/* + n = intervals.len() + 時間計算量: O(n log n) + 空間計算量: O(1) +*/ + +/* + can_attend_meetingsの実装のみの時間 + 1回目: 1分43秒 + 2回目: 1分03秒 + 3回目: 0分56秒 +*/ + +/* + 所感 + - 書籍「Effective Rust」で紹介されていたthiserrorクレートを試せたのが良かった。 + - NeetCodeに問題が存在していてRust言語でも採点システムを通せることを途中まで知らなかったので、採点システムの制限を受けずにある程度自由に書けたのが良かった。 +*/ + +use thiserror::Error; + +#[derive(Error, Debug, PartialEq)] +pub enum IntervalError { + #[error("invalid interval. end must be greater start. start: {0}, end: {1}")] + InvalidInterval(u32, u32), +} + +#[derive(Debug)] +pub struct Interval { + start: u32, + end: u32, +} +impl Interval { + pub fn new(start: u32, end: u32) -> Result { + if end <= start { + return Err(IntervalError::InvalidInterval(start, end)); + } + + Ok(Self { start, end }) + } +} + +pub struct Solution {} +impl Solution { + pub fn can_attend_meetings(mut intervals: Vec) -> bool { + intervals.sort_by_key(|v| v.start); + intervals.windows(2).all(|w| { + let (interval, next_interval) = (&w[0], &w[1]); + interval.end <= next_interval.start + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn to_interval(interval: (u32, u32)) -> Interval { + Interval::new(interval.0, interval.1).unwrap() + } + + #[test] + fn step3_test() { + let intervals = vec![(0, 30), (5, 10), (15, 20)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::can_attend_meetings(intervals), false); + + let intervals = vec![(5, 8), (9, 15)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::can_attend_meetings(intervals), true); + + let intervals = vec![(5, 8), (8, 9)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::can_attend_meetings(intervals), true); + + let intervals = vec![(1, 2), (2, 4), (2, 3)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::can_attend_meetings(intervals), false); + } + + #[test] + fn invalid_interval_test() { + let (start, end) = (1, 0); + let error = Interval::new(start, end).unwrap_err(); + assert_eq!(error, IntervalError::InvalidInterval(start, end)); + + let (start, end) = (1, 1); + let error = Interval::new(start, end).unwrap_err(); + assert_eq!(error, IntervalError::InvalidInterval(start, end)); + } + + // GPT-5.2によって生成 + #[test] + fn additional_cases_test() { + // empty: meetings無しなら参加可能(仕様として自然) + let intervals = vec![].into_iter().map(to_interval).collect::>(); + assert_eq!(Solution::can_attend_meetings(intervals), true); + + // single: 1件なら参加可能 + let intervals = vec![(1, 2)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::can_attend_meetings(intervals), true); + + // unsorted but non-overlapping: ソートして判定できるか + let intervals = vec![(10, 12), (1, 3), (4, 9)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::can_attend_meetings(intervals), true); + + // unsorted with overlap + let intervals = vec![(10, 12), (1, 5), (4, 9)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::can_attend_meetings(intervals), false); + + // fully contained: 内包((2,3) が (1,10) に含まれる) + let intervals = vec![(1, 10), (2, 3)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::can_attend_meetings(intervals), false); + + // same start: 開始時刻が同一なら必ず重なる(endが異なる) + let intervals = vec![(5, 7), (5, 6)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::can_attend_meetings(intervals), false); + + // same interval duplicated + let intervals = vec![(1, 2), (1, 2)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::can_attend_meetings(intervals), false); + + // touching chain: 端点で接するのはOK(半開区間想定) + let intervals = vec![(0, 1), (1, 2), (2, 3), (3, 10)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::can_attend_meetings(intervals), true); + + // overlap by 1: 1だけ重なる + let intervals = vec![(0, 2), (1, 3)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::can_attend_meetings(intervals), false); + + // large numbers + let intervals = vec![ + (0, 1), + (1_000_000_000, 1_000_000_100), + (1_000_000_100, 2_000_000_000), + ] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::can_attend_meetings(intervals), true); + + // long range conflicts with later small range + let intervals = vec![(0, 100), (50, 60), (100, 120)] + .into_iter() + .map(to_interval) + .collect::>(); + assert_eq!(Solution::can_attend_meetings(intervals), false); + } +}