diff --git a/253_meeting_rooms_ii_medium/README.md b/253_meeting_rooms_ii_medium/README.md new file mode 100644 index 0000000..032f7e3 --- /dev/null +++ b/253_meeting_rooms_ii_medium/README.md @@ -0,0 +1,193 @@ +# 問題へのリンク +[253. Meeting Rooms II](https://leetcode.com/problems/meeting-rooms-ii/) + +# 言語 +Python + +# 問題の概要 + +会議室のスケジュールを管理するために、最小限の会議室を確保するとき、いくつの会議室が必要かを求める問題。各会議は開始時刻と終了時刻で表され、すべての会議が重複しないように会議室を割り当てる必要がある。 + +# 自分の解法 + +## step1 + +```python +from heapq import heappop, heappush + + +class Solution: + def minMeetingRooms(self, intervals: list[list[int]]) -> int: + if not intervals: + return 0 + intervals.sort() + start, end = intervals[0] + room_end_times: list[int] = [end] + for i, (start, end) in enumerate(intervals): + if i == 0: + continue + earliest_end = room_end_times[0] + # insert a new interval into existing room if available + if earliest_end <= start: + heappush(room_end_times, end) + heappop(room_end_times) # remove previous endtime + else: + # if not available, then a new room is required + heappush(room_end_times, end) + + return len(room_end_times) +``` + + +- 時間計算量:`O(n log n)` +- 空間計算量:`O(n)` + +## step2 + +```python +from heapq import heappush, heapreplace + + +class Solution: + def minMeetingRooms(self, intervals: list[list[int]]) -> int: + if not intervals: + return 0 + + intervals.sort() + start, end = intervals[0] + meeting_end_times = [] + heappush(meeting_end_times, end) + for start, end in intervals[1:]: + earliest_end = meeting_end_times[0] + # If the earliest available room is free, reuse it. + if earliest_end <= start: + heapreplace(meeting_end_times, end) + # Otherwise, allocate a new room + else: + heappush(meeting_end_times, end) + + return len(meeting_end_times) +``` + +- `heappush`と`heappop`を使用する代わりに`heapreplace`を使用することで、ヒープの操作を効率化&コードが明快に。 + - なぜ効率的か? + - `heappop`はヒープの最小要素を削除したあと、ヒープの最後の要素を根に移動し、ヒープ木を再構築する + - `heappush`も新しい要素を追加したあと、ヒープ木を再構築する + - `heapreplace`は最小要素を削除して新しい要素を追加する操作を1回で行うため、効率的である。 + + +## step3 + +`step3_1.py`:一度思いついてしまえばヒープよりも簡単に実装できる +```python +class Solution: + def minMeetingRooms(self, intervals: list[list[int]]) -> int: + time_events = [] + # END must comes first when time_events is sorted. + START = 1 + END = 0 + for start_time, end_time in intervals: + time_events.append((start_time, START)) + time_events.append((end_time, END)) + time_events.sort() + num_rooms = 0 + max_num_rooms = 0 + for _, event_type in time_events: + if event_type == END: + num_rooms -= 1 + else: # START + num_rooms += 1 + max_num_rooms = max(max_num_rooms, num_rooms) + return max_num_rooms +``` + +`step3_2.py`:ヒープを用いた解法 +```python +class Solution: + def minMeetingRooms(self, intervals: list[list[int]]) -> int: + if not intervals: + return 0 + intervals.sort() + start_time, end_time = intervals[0] + heap = [] + heappush(heap, end_time) + for start_time, end_time in intervals[1:]: + earliest_end_time = heap[0] + if start_time < earliest_end_time: + heappush(heap, end_time) + continue + # use the same room after the earliest meeting ends + heappushpop(heap, end_time) + return len(heap) +``` + +## step4 (FB) +- `times`は`os`モジュールにあるので、変数名を変更した方が良い + - cf. https://docs.python.org/ja/3.13/library/os.html#os.times + + + + +# 別解・模範解答 + +## Timeline Sweep + +```python +class Solution: + def minMeetingRooms(self, intervals: list[list[int]]) -> int: + meeting_events = [] + # meeting events will be sorted in ascending order + # so end must come ahead + START = 1 + END = 0 + for start_time, end_time in intervals: + meeting_events.append((start_time, START)) + meeting_events.append((end_time, END)) + + meeting_events.sort() + + max_rooms = 0 + num_rooms = 0 + for _, event_type in meeting_events: + if event_type == START: + num_rooms += 1 + if event_type == END: + num_rooms -= 1 + max_rooms = max(max_rooms, num_rooms) + return max_rooms +``` + + +- 時間計算量:`O(n log n)` +- 空間計算量:`O(n)` + +## Two Pointers + +```python +class Solution: + def minMeetingRooms(self, intervals: list[list[int]]) -> int: + start_times: list[int] = [interval[0] for interval in intervals] + end_times: list[int] = [interval[1] for interval in intervals] + + start_times.sort() + end_times.sort() + max_rooms = 0 + start_time_pointer = 0 + end_time_pointer = 0 + while start_time_pointer < len(intervals): + start_time = start_times[start_time_pointer] + end_time = end_times[end_time_pointer] + if start_time < end_time: + start_time_pointer += 1 + else: + end_time_pointer += 1 + max_rooms = max(max_rooms, start_time_pointer - end_time_pointer) + return max_rooms +``` + +- LeetCodeに掲載されている模範解答。しかしTimeline Sweepの方がコードが明快で理解しやすいと感じた。 +- 時間計算量:`O(n log n)` +- 空間計算量:`O(n)` + +# 次に解く問題の予告 +- [142. Linked List Cycle II](https://leetcode.com/problems/linked-list-cycle-ii/) diff --git a/253_meeting_rooms_ii_medium/step1.py b/253_meeting_rooms_ii_medium/step1.py new file mode 100644 index 0000000..9a432c9 --- /dev/null +++ b/253_meeting_rooms_ii_medium/step1.py @@ -0,0 +1,34 @@ +# +# @lc app=leetcode id=253 lang=python3 +# +# [253] Meeting Rooms II +# + + +# @lc code=start +from heapq import heappop, heappush + + +class Solution: + def minMeetingRooms(self, intervals: list[list[int]]) -> int: + if not intervals: + return 0 + intervals.sort() + start, end = intervals[0] + room_end_times: list[int] = [end] + for i, (start, end) in enumerate(intervals): + if i == 0: + continue + earliest_end = room_end_times[0] + # insert a new interval into existing room if available + if earliest_end <= start: + heappush(room_end_times, end) + heappop(room_end_times) # remove previous endtime + else: + # if not available, then a new room is required + heappush(room_end_times, end) + + return len(room_end_times) + + +# @lc code=end diff --git a/253_meeting_rooms_ii_medium/step2.py b/253_meeting_rooms_ii_medium/step2.py new file mode 100644 index 0000000..effc745 --- /dev/null +++ b/253_meeting_rooms_ii_medium/step2.py @@ -0,0 +1,33 @@ +# +# @lc app=leetcode id=253 lang=python3 +# +# [253] Meeting Rooms II +# + +# @lc code=start + +from heapq import heappush, heapreplace + + +class Solution: + def minMeetingRooms(self, intervals: list[list[int]]) -> int: + if not intervals: + return 0 + + intervals.sort() + start, end = intervals[0] + meeting_end_times = [] + heappush(meeting_end_times, end) + for start, end in intervals[1:]: + earliest_end = meeting_end_times[0] + # If the earliest available room is free, reuse it. + if earliest_end <= start: + heapreplace(meeting_end_times, end) + # Otherwise, allocate a new room + else: + heappush(meeting_end_times, end) + + return len(meeting_end_times) + + +# @lc code=end diff --git a/253_meeting_rooms_ii_medium/step3_1.py b/253_meeting_rooms_ii_medium/step3_1.py new file mode 100644 index 0000000..0046109 --- /dev/null +++ b/253_meeting_rooms_ii_medium/step3_1.py @@ -0,0 +1,29 @@ +# +# @lc app=leetcode id=253 lang=python3 +# +# [253] Meeting Rooms II +# + +# @lc code=start +class Solution: + def minMeetingRooms(self, intervals: list[list[int]]) -> int: + time_events = [] + # END must comes first when time_events is sorted. + START = 1 + END = 0 + for start_time, end_time in intervals: + time_events.append((start_time, START)) + time_events.append((end_time, END)) + time_events.sort() + num_rooms = 0 + max_num_rooms = 0 + for _, event_type in time_events: + if event_type == END: + num_rooms -= 1 + else: # START + num_rooms += 1 + max_num_rooms = max(max_num_rooms, num_rooms) + return max_num_rooms + + +# @lc code=end diff --git a/253_meeting_rooms_ii_medium/step3_2.py b/253_meeting_rooms_ii_medium/step3_2.py new file mode 100644 index 0000000..cdc2171 --- /dev/null +++ b/253_meeting_rooms_ii_medium/step3_2.py @@ -0,0 +1,29 @@ +# +# @lc app=leetcode id=253 lang=python3 +# +# [253] Meeting Rooms II +# + +# @lc code=start +from heapq import heappush, heappushpop + + +class Solution: + def minMeetingRooms(self, intervals: list[list[int]]) -> int: + if not intervals: + return 0 + intervals.sort() + start_time, end_time = intervals[0] + heap = [] + heappush(heap, end_time) + for start_time, end_time in intervals[1:]: + earliest_end_time = heap[0] + if start_time < earliest_end_time: + heappush(heap, end_time) + continue + # use the same room after the earliest meeting ends + heappushpop(heap, end_time) + return len(heap) + + +# @lc code=end diff --git a/253_meeting_rooms_ii_medium/timeline_sweep.py b/253_meeting_rooms_ii_medium/timeline_sweep.py new file mode 100644 index 0000000..4a3a5fc --- /dev/null +++ b/253_meeting_rooms_ii_medium/timeline_sweep.py @@ -0,0 +1,32 @@ +# +# @lc app=leetcode id=253 lang=python3 +# +# [253] Meeting Rooms II +# + +# @lc code=start +class Solution: + def minMeetingRooms(self, intervals: list[list[int]]) -> int: + meeting_events = [] + # meeting events will be sorted in ascending order + # so end must come ahead + START = 1 + END = 0 + for start_time, end_time in intervals: + meeting_events.append((start_time, START)) + meeting_events.append((end_time, END)) + + meeting_events.sort() + + max_rooms = 0 + num_rooms = 0 + for _, event_type in meeting_events: + if event_type == START: + num_rooms += 1 + if event_type == END: + num_rooms -= 1 + max_rooms = max(max_rooms, num_rooms) + return max_rooms + + +# @lc code=end diff --git a/253_meeting_rooms_ii_medium/two_pointers.py b/253_meeting_rooms_ii_medium/two_pointers.py new file mode 100644 index 0000000..d0bf1dc --- /dev/null +++ b/253_meeting_rooms_ii_medium/two_pointers.py @@ -0,0 +1,29 @@ +# +# @lc app=leetcode id=253 lang=python3 +# +# [253] Meeting Rooms II +# + +# @lc code=start +class Solution: + def minMeetingRooms(self, intervals: list[list[int]]) -> int: + start_times: list[int] = [interval[0] for interval in intervals] + end_times: list[int] = [interval[1] for interval in intervals] + + start_times.sort() + end_times.sort() + max_rooms = 0 + start_time_pointer = 0 + end_time_pointer = 0 + while start_time_pointer < len(intervals): + start_time = start_times[start_time_pointer] + end_time = end_times[end_time_pointer] + if start_time < end_time: + start_time_pointer += 1 + else: + end_time_pointer += 1 + max_rooms = max(max_rooms, start_time_pointer - end_time_pointer) + return max_rooms + + +# @lc code=end