-
Notifications
You must be signed in to change notification settings - Fork 0
solve: 253.Meeting Rooms Ⅱ #56
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
Open
t9a-dev
wants to merge
1
commit into
main
Choose a base branch
from
253.Meeting-Rooms-II
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,7 @@ | ||
| [package] | ||
| name= "leetcode-practice" | ||
| version = "0.1.0" | ||
| edition = "2021" | ||
| edition = "2021" | ||
|
|
||
| [dependencies] | ||
| thiserror = "2.0.18" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,150 @@ | ||
| // Step1 | ||
| // 目的: 方法を思いつく | ||
|
|
||
| // 方法 | ||
| // 5分考えてわからなかったら答えをみる | ||
| // 答えを見て理解したと思ったら全部消して答えを隠して書く | ||
| // 5分筆が止まったらもう一回みて全部消す | ||
| // 正解したら終わり | ||
|
|
||
| /* | ||
| 問題の理解 | ||
| - 会議の開始時間、終了時間を表すInterval構造体からなる配列intervalsが入力として与えられるので、全ての会議が重複しないために必要な最小の会議室の数を返す。 | ||
| [(start1,end1), (start2,end2))] のとして、[(0,3), (0,2)] のとき必要な会議室の数は2になる。 | ||
|
|
||
| 何を考えて解いていたか | ||
| - 全問と同じ考え方で、会議の開始時間でソートして重複を確認する。会議時間の重複を見つけるたびに必要な会議室の数をインクリメントしていけば良さそう。 | ||
| - intervalsが空であれば0を返すエッジケースの処理が必要そう。intervalsが空でなければ会議室の数は1からスタートするので。 | ||
| n = intervals.len() | ||
| 時間計算量: O(n log n) | ||
| 空間計算量: O(1) | ||
| 重複した時に、数え上げる方向で本当に良いのか不安を感じるので、コードを書く前にできる範囲でテストケースを考えて問題なさそうか確認する。 | ||
| [(0,5)] out=1 | ||
| [(0,5), (0,2)] out=2 | ||
| [(0,5), (5,7)] out=1 | ||
| [(0,5), (0,2), (5,7)] out=2 | ||
| [(0,5), (0,2), (1,5), (5,7)] out=3 | ||
| この考え方で大丈夫そうに見える。 | ||
|
|
||
| 提出したところ、次のテストケースでWrong Answerとなった。 | ||
| intervals=[(1,5), (2,6), (3,7), (4,8), (5,9)] actual=5 expect=4 | ||
| (1,5), (5,9)をうまく処理できていないように見える。(1,5)の会議終わりで、(5,9)が使えるのにも関わらずインクリメントしてしまっている。 | ||
| つまり、会議の開始時間でソートして隣接する会議時間を見るだけでは正しい答えが得られないようにみえる。 | ||
| 他の解法が思いつかないので、step1a.rsで他の人のコードを見て解法を理解する。 | ||
|
|
||
| 何がわからなかったか | ||
| - 単にソートして数え上げるだけでは対応できないケースがあることを見抜けなかった。 | ||
| */ | ||
|
|
||
| use thiserror::Error; | ||
|
|
||
| #[derive(Error, Debug, PartialEq)] | ||
| pub enum IntervalError { | ||
| #[error("invalid interval. end must be greater start. start: {0}, end: {1}")] | ||
| InvalidIntervalRange(i32, i32), | ||
| #[error("invalid interval. start and end must be greater than 0. start: {0}, end: {1}")] | ||
| InvalidIntervalValue(i32, i32), | ||
| } | ||
|
|
||
| #[derive(Debug)] | ||
| pub struct Interval { | ||
| start: i32, | ||
| end: i32, | ||
| } | ||
| impl Interval { | ||
| pub fn new(start: i32, end: i32) -> Result<Self, IntervalError> { | ||
| if start < 0 || end < 0 { | ||
| return Err(IntervalError::InvalidIntervalValue(start, end)); | ||
| } | ||
| if end <= start { | ||
| return Err(IntervalError::InvalidIntervalRange(start, end)); | ||
| } | ||
|
|
||
| Ok(Self { start, end }) | ||
| } | ||
| } | ||
|
|
||
| pub struct Solution {} | ||
| impl Solution { | ||
| pub fn min_meeting_rooms(mut intervals: Vec<Interval>) -> i32 { | ||
| /* | ||
| このコードはWrong Answerとなるコードです。 | ||
| intervals=[(1,5), (2,6), (3,7), (4,8), (5,9)] actual=5 expect=4 | ||
| */ | ||
| if intervals.is_empty() { | ||
| return 0; | ||
| } | ||
|
|
||
| intervals.sort_by_key(|v| v.start); | ||
|
|
||
| let mut min_required_meeting_rooms_count = 1; | ||
| for w in intervals.windows(2) { | ||
| let (interval, next_interval) = (&w[0], &w[1]); | ||
| if next_interval.start < interval.end { | ||
| min_required_meeting_rooms_count += 1; | ||
| } | ||
| } | ||
|
|
||
| min_required_meeting_rooms_count | ||
| } | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use super::*; | ||
|
|
||
| fn to_interval(interval: (i32, i32)) -> Interval { | ||
| Interval::new(interval.0, interval.1).unwrap() | ||
| } | ||
|
|
||
| #[test] | ||
| fn step1_conflict_intervals_test() { | ||
| let intervals = vec![(0, 40), (5, 10), (15, 20)] | ||
| .into_iter() | ||
| .map(to_interval) | ||
| .collect::<Vec<_>>(); | ||
| assert_eq!(Solution::min_meeting_rooms(intervals), 2); | ||
| } | ||
|
|
||
| #[test] | ||
| fn step1_no_conflict_intervals_test() { | ||
| let intervals = vec![(0, 40)] | ||
| .into_iter() | ||
| .map(to_interval) | ||
| .collect::<Vec<_>>(); | ||
| assert_eq!(Solution::min_meeting_rooms(intervals), 1); | ||
|
|
||
| let intervals = vec![].into_iter().map(to_interval).collect::<Vec<_>>(); | ||
| assert_eq!(Solution::min_meeting_rooms(intervals), 0); | ||
| } | ||
|
|
||
| #[test] | ||
| fn step1_invalid_interval_range_test() { | ||
| let (start, end) = (1, 0); | ||
| assert_eq!( | ||
| Interval::new(start, end).unwrap_err(), | ||
| IntervalError::InvalidIntervalRange(start, end) | ||
| ); | ||
|
|
||
| let (start, end) = (10, 10); | ||
| assert_eq!( | ||
| Interval::new(start, end).unwrap_err(), | ||
| IntervalError::InvalidIntervalRange(start, end) | ||
| ); | ||
| } | ||
|
|
||
| #[test] | ||
| fn step1_invalid_interval_value_test() { | ||
| let (start, end) = (-1, 1); | ||
| assert_eq!( | ||
| Interval::new(start, end).unwrap_err(), | ||
| IntervalError::InvalidIntervalValue(start, end) | ||
| ); | ||
|
|
||
| let (start, end) = (-2, 0); | ||
| assert_eq!( | ||
| Interval::new(start, end).unwrap_err(), | ||
| IntervalError::InvalidIntervalValue(start, end) | ||
| ); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,173 @@ | ||
| // Step1a | ||
| // 目的: 答えを見て解法を理解する | ||
|
|
||
| /* | ||
| https://www.youtube.com/watch?v=FdzJmTCVyJU | ||
| - NeetCodeの解法 | ||
| - 動画を見ていたら、heapを使う解法で解けそうなことに気付いた。 | ||
| - 利用中の会議室の数を会議時間の開始で+1,終了で-1する。 | ||
| - 会議が新たに開始する時に、必要な会議室の最大値を更新する。 | ||
|
|
||
| 所感 | ||
| - この考え方で実装してAcceptedになったが、[(1, 5), (2, 6), (3, 7), (4, 8), (5, 9)]をmin_heapにpush(),pop()したときの挙動を理解しきれていないことに気付いた。 | ||
| - 会議の開始、終了時間をそれぞれtupleの1つ目の要素としてmin_heapにpushしていくと、 | ||
| - [(1,1), (2,1), (3,1), (4,1), (5,1), (5,-1) ...]になると思っていた。tupleの1つ目の要素が同一の場合はpush順で並ぶと思い込んでいたため。実際には(5,-1)の方が先に来る。 | ||
| - [(1,1), (2,1), (3,1), (4,1), (5,-1), (5,1) ...]になっている。 | ||
| - この挙動のおかげで自身の書いたロジックが動いているようにみえる。 | ||
| - 公式ドキュメントにtupleのOrd,PartialOrdでの比較時には最初に等しくない値を見つけるまでタプルの値を順番に見ていくという記述があった。 | ||
| https://doc.rust-lang.org/std/primitive.tuple.html | ||
| > The sequential nature of the tuple applies to its implementations of various traits. For example, in PartialOrd and Ord, the elements are compared sequentially until the first non-equal set is found. | ||
| - この点を知らない状態で意識せずに実装していたので、たまたま正解した結果になってしまった。 | ||
| - この視点で見ると実装がパズルに見える。tuple比較時の挙動を正確に把握していないとロジックが動いている理由が正確に把握できないため。 | ||
| */ | ||
|
|
||
| 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}")] | ||
| InvalidIntervalRange(i32, i32), | ||
| #[error("invalid interval. start and end must be greater than 0. start: {0}, end: {1}")] | ||
| InvalidIntervalValue(i32, i32), | ||
| } | ||
|
|
||
| #[derive(Debug)] | ||
| pub struct Interval { | ||
| start: i32, | ||
| end: i32, | ||
| } | ||
| impl Interval { | ||
| pub fn new(start: i32, end: i32) -> Result<Self, IntervalError> { | ||
| if start < 0 || end < 0 { | ||
| return Err(IntervalError::InvalidIntervalValue(start, end)); | ||
| } | ||
| if end <= start { | ||
| return Err(IntervalError::InvalidIntervalRange(start, end)); | ||
| } | ||
|
|
||
| Ok(Self { start, end }) | ||
| } | ||
| } | ||
|
|
||
| pub struct Solution {} | ||
| impl Solution { | ||
| pub fn min_meeting_rooms(intervals: Vec<Interval>) -> i32 { | ||
| if intervals.is_empty() { | ||
| return 0; | ||
| } | ||
|
|
||
| let mut time_to_use_room_count = BinaryHeap::new(); | ||
| for interval in intervals { | ||
| time_to_use_room_count.push(Reverse((interval.start, 1))); | ||
| time_to_use_room_count.push(Reverse((interval.end, -1))); | ||
| } | ||
|
|
||
| let mut min_meeting_rooms_count = 0; | ||
| let mut using_meeting_rooms_count = 0; | ||
| while let Some(Reverse((_, use_room_count))) = time_to_use_room_count.pop() { | ||
| using_meeting_rooms_count += use_room_count; | ||
|
|
||
| let use_meeting_room = use_room_count == 1; | ||
| if use_meeting_room { | ||
| min_meeting_rooms_count = min_meeting_rooms_count.max(using_meeting_rooms_count); | ||
| } | ||
| } | ||
|
|
||
| min_meeting_rooms_count | ||
| } | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use super::*; | ||
|
|
||
| fn to_interval(interval: (i32, i32)) -> Interval { | ||
| Interval::new(interval.0, interval.1).unwrap() | ||
| } | ||
|
|
||
| #[test] | ||
| fn play_ground() { | ||
| let intervals = vec![(1, 5), (2, 6), (3, 7), (4, 8), (5, 9)] | ||
| .into_iter() | ||
| .map(to_interval) | ||
| .collect::<Vec<_>>(); | ||
| let mut min_heap = BinaryHeap::new(); | ||
| for interval in intervals { | ||
| min_heap.push(Reverse((interval.start, 1))); | ||
| min_heap.push(Reverse((interval.end, -1))); | ||
| } | ||
|
|
||
| assert_eq!(Reverse((1, 1)), min_heap.pop().unwrap()); | ||
| assert_eq!(Reverse((2, 1)), min_heap.pop().unwrap()); | ||
| assert_eq!(Reverse((3, 1)), min_heap.pop().unwrap()); | ||
| assert_eq!(Reverse((4, 1)), min_heap.pop().unwrap()); | ||
| // tupleは1つ目の要素T1が等しい時、次の要素T2で比較する。 | ||
| // なので、5が重複すると次の要素である-1,1の比較によって順序が決まる。 | ||
| // https://doc.rust-lang.org/std/primitive.tuple.html | ||
| assert_eq!(Reverse((5, -1)), min_heap.pop().unwrap()); | ||
| assert_eq!(Reverse((5, 1)), min_heap.pop().unwrap()); | ||
| assert_eq!(Reverse((6, -1)), min_heap.pop().unwrap()); | ||
| assert_eq!(Reverse((7, -1)), min_heap.pop().unwrap()); | ||
| assert_eq!(Reverse((8, -1)), min_heap.pop().unwrap()); | ||
| assert_eq!(Reverse((9, -1)), min_heap.pop().unwrap()); | ||
| } | ||
|
|
||
| #[test] | ||
| fn step1a_conflict_intervals_test() { | ||
| let intervals = vec![(1, 5), (2, 6), (3, 7), (4, 8), (5, 9)] | ||
| .into_iter() | ||
| .map(to_interval) | ||
| .collect::<Vec<_>>(); | ||
| assert_eq!(Solution::min_meeting_rooms(intervals), 4); | ||
|
|
||
| let intervals = vec![(0, 40), (5, 10), (15, 20)] | ||
| .into_iter() | ||
| .map(to_interval) | ||
| .collect::<Vec<_>>(); | ||
| assert_eq!(Solution::min_meeting_rooms(intervals), 2); | ||
| } | ||
|
|
||
| #[test] | ||
| fn step1a_no_conflict_intervals_test() { | ||
| let intervals = vec![(0, 40)] | ||
| .into_iter() | ||
| .map(to_interval) | ||
| .collect::<Vec<_>>(); | ||
| assert_eq!(Solution::min_meeting_rooms(intervals), 1); | ||
|
|
||
| let intervals = vec![].into_iter().map(to_interval).collect::<Vec<_>>(); | ||
| assert_eq!(Solution::min_meeting_rooms(intervals), 0); | ||
| } | ||
|
|
||
| #[test] | ||
| fn step1a_invalid_interval_range_test() { | ||
| let (start, end) = (1, 0); | ||
| assert_eq!( | ||
| Interval::new(start, end).unwrap_err(), | ||
| IntervalError::InvalidIntervalRange(start, end) | ||
| ); | ||
|
|
||
| let (start, end) = (10, 10); | ||
| assert_eq!( | ||
| Interval::new(start, end).unwrap_err(), | ||
| IntervalError::InvalidIntervalRange(start, end) | ||
| ); | ||
| } | ||
|
|
||
| #[test] | ||
| fn step1a_invalid_interval_value_test() { | ||
| let (start, end) = (-1, 1); | ||
| assert_eq!( | ||
| Interval::new(start, end).unwrap_err(), | ||
| IntervalError::InvalidIntervalValue(start, end) | ||
| ); | ||
|
|
||
| let (start, end) = (-2, 0); | ||
| assert_eq!( | ||
| Interval::new(start, end).unwrap_err(), | ||
| IntervalError::InvalidIntervalValue(start, end) | ||
| ); | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
変数名がやや冗長に感じました。自分なら min_rooms と num_rooms にすると思います。