Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion Cargo.toml
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"
150 changes: 150 additions & 0 deletions src/bin/step1.rs
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)
);
}
}
173 changes: 173 additions & 0 deletions src/bin/step1a.rs
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;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

変数名がやや冗長に感じました。自分なら min_rooms と num_rooms にすると思います。

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)
);
}
}
Loading