Skip to content

Commit 414e19d

Browse files
authored
Merge pull request #171 from BrianLusina/feat/algorithms-intervals
feat(algorithms, intervals): most booked meeting rooms
2 parents d8bc502 + ac30fd4 commit 414e19d

24 files changed

Lines changed: 229 additions & 2 deletions

DIRECTORY.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,7 @@
237237
* [Test Intervals Intersection](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/intervals/interval_intersection/test_intervals_intersection.py)
238238
* Meeting Rooms
239239
* [Test Min Meeting Rooms](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/intervals/meeting_rooms/test_min_meeting_rooms.py)
240+
* [Test Most Booked Meeting Rooms](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/intervals/meeting_rooms/test_most_booked_meeting_rooms.py)
240241
* Merge Intervals
241242
* [Test Merge Intervals](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/intervals/merge_intervals/test_merge_intervals.py)
242243
* Non Overlapping Intervals
@@ -451,9 +452,11 @@
451452
* [Test My Hashset](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/hashset/test_my_hashset.py)
452453
* Linked Lists
453454
* Circular
455+
* [Circular Linked List Utils](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/linked_lists/circular/circular_linked_list_utils.py)
454456
* [Node](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/linked_lists/circular/node.py)
455457
* [Test Circular Linked List](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/linked_lists/circular/test_circular_linked_list.py)
456458
* [Test Circular Linked List Split](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/linked_lists/circular/test_circular_linked_list_split.py)
459+
* [Test Circular Linked List Utils](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/linked_lists/circular/test_circular_linked_list_utils.py)
457460
* Doubly Linked List
458461
* [Node](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/linked_lists/doubly_linked_list/node.py)
459462
* [Test Doubly Linked List](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/linked_lists/doubly_linked_list/test_doubly_linked_list.py)
@@ -911,6 +914,8 @@
911914
* [Test Reverse Integer](https://github.com/BrianLusina/PythonSnips/blob/master/pymath/reverse_integer/test_reverse_integer.py)
912915
* Self Crossing
913916
* [Test Is Self Crossing](https://github.com/BrianLusina/PythonSnips/blob/master/pymath/self_crossing/test_is_self_crossing.py)
917+
* Sum Of Multiples
918+
* [Test Sum Of Multiples](https://github.com/BrianLusina/PythonSnips/blob/master/pymath/sum_of_multiples/test_sum_of_multiples.py)
914919
* Super Size
915920
* [Test Super Size](https://github.com/BrianLusina/PythonSnips/blob/master/pymath/super_size/test_super_size.py)
916921
* Triangles
@@ -942,6 +947,8 @@
942947
* [Test First Occurrence](https://github.com/BrianLusina/PythonSnips/blob/master/pystrings/first_occurrence/test_first_occurrence.py)
943948
* Greatest Common Divisor
944949
* [Test Greatest Common Divisor](https://github.com/BrianLusina/PythonSnips/blob/master/pystrings/greatest_common_divisor/test_greatest_common_divisor.py)
950+
* Integer To English
951+
* [Test Integer To English](https://github.com/BrianLusina/PythonSnips/blob/master/pystrings/integer_to_english/test_integer_to_english.py)
945952
* Inttostr
946953
* [Test Int To Str](https://github.com/BrianLusina/PythonSnips/blob/master/pystrings/inttostr/test_int_to_str.py)
947954
* Is Prefix
@@ -1207,7 +1214,6 @@
12071214
* [Test Sieve Of Erastothenes](https://github.com/BrianLusina/PythonSnips/blob/master/tests/pymath/test_sieve_of_erastothenes.py)
12081215
* [Test Split Even Numbers](https://github.com/BrianLusina/PythonSnips/blob/master/tests/pymath/test_split_even_numbers.py)
12091216
* [Test Sum Between](https://github.com/BrianLusina/PythonSnips/blob/master/tests/pymath/test_sum_between.py)
1210-
* [Test Sum Of Multiples](https://github.com/BrianLusina/PythonSnips/blob/master/tests/pymath/test_sum_of_multiples.py)
12111217
* [Test Sum Same](https://github.com/BrianLusina/PythonSnips/blob/master/tests/pymath/test_sum_same.py)
12121218
* [Test Summation](https://github.com/BrianLusina/PythonSnips/blob/master/tests/pymath/test_summation.py)
12131219
* [Test Tank Truck](https://github.com/BrianLusina/PythonSnips/blob/master/tests/pymath/test_tank_truck.py)

algorithms/intervals/meeting_rooms/README.md

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,3 +114,156 @@ becomes O(n log(n)) + O(n log(n)) = O(n log(n)).
114114

115115
The space complexity of this solution is O(n). This is because we are building a min-heap that, in the worst case,
116116
can have all the input elements. Therefore, the space required to compute this solution would be O(n).
117+
118+
---
119+
# Meeting Rooms III
120+
121+
Given an integer, rooms, which represents the total number of rooms, where each room is numbered from 0 to rooms - 1.
122+
Additionally, you are given a 2D integer array called meetings, where each element meetings[i] =
123+
[starti, endi] indicates that a meeting will be held in the half-closed interval [starti, endi). Each starti is unique.
124+
125+
Meetings are allocated to rooms in the following manner:
126+
127+
1. Each meeting will take place in the unused room with the lowest number.
128+
2. If there are no available rooms, the meeting will be delayed until a room becomes free. The delayed meeting should
129+
have the same duration as the original meeting.
130+
3. When a room is vacated, the meeting with the earliest original start time is given priority for that room.
131+
132+
Your task is to determine the room number that hosted the most meetings. If there are multiple rooms, return the room
133+
with the lowest number.
134+
135+
> Note: A half-closed interval [a, b) is the interval between a and b, including a and not including b.
136+
137+
## Constraints
138+
139+
- 1 ≤ rooms ≤ 100
140+
- 1 ≤ meetings.length ≤ 1000
141+
- meetings[i].length == 2
142+
- 0 ≤ starti < endi ≤ 10000
143+
- All the values of `starti` are unique
144+
145+
## Examples
146+
147+
![Example 1](./images/examples/meeting_rooms_3_example_1.png)
148+
![Example 2](./images/examples/meeting_rooms_3_example_3.png)
149+
![Example 3](./images/examples/meeting_rooms_3_example_3.png)
150+
![Example 4](./images/examples/meeting_rooms_3_example_4.png)
151+
152+
Example 5:
153+
```text
154+
Input: n = 2, meetings = [[0,10],[1,5],[2,7],[3,4]]
155+
Output: 0
156+
Explanation:
157+
- At time 0, both rooms are not being used. The first meeting starts in room 0.
158+
- At time 1, only room 1 is not being used. The second meeting starts in room 1.
159+
- At time 2, both rooms are being used. The third meeting is delayed.
160+
- At time 3, both rooms are being used. The fourth meeting is delayed.
161+
- At time 5, the meeting in room 1 finishes. The third meeting starts in room 1 for the time period [5,10).
162+
- At time 10, the meetings in both rooms finish. The fourth meeting starts in room 0 for the time period [10,11).
163+
Both rooms 0 and 1 held 2 meetings, so we return 0.
164+
```
165+
166+
Example 6:
167+
```text
168+
Input: n = 3, meetings = [[1,20],[2,10],[3,5],[4,9],[6,8]]
169+
Output: 1
170+
Explanation:
171+
- At time 1, all three rooms are not being used. The first meeting starts in room 0.
172+
- At time 2, rooms 1 and 2 are not being used. The second meeting starts in room 1.
173+
- At time 3, only room 2 is not being used. The third meeting starts in room 2.
174+
- At time 4, all three rooms are being used. The fourth meeting is delayed.
175+
- At time 5, the meeting in room 2 finishes. The fourth meeting starts in room 2 for the time period [5,10).
176+
- At time 6, all three rooms are being used. The fifth meeting is delayed.
177+
- At time 10, the meetings in rooms 1 and 2 finish. The fifth meeting starts in room 1 for the time period [10,12).
178+
Room 0 held 1 meeting while rooms 1 and 2 each held 2 meetings, so we return 1.
179+
```
180+
181+
## Topics
182+
183+
- Array
184+
- Hash Table
185+
- Sorting
186+
- Heap (Priority Queue)
187+
- Simulation
188+
189+
## Hints
190+
191+
- Sort meetings based on start times.
192+
- Use two min heaps, the first one keeps track of the numbers of all the rooms that are free. The second heap keeps
193+
track of the end times of all the meetings that are happening and the room that they are in.
194+
- Keep track of the number of times each room is used in an array.
195+
- With each meeting, check if there are any free rooms. If there are, then use the room with the smallest number.
196+
Otherwise, assign the meeting to the room whose meeting will end the soonest.
197+
198+
## Solution
199+
200+
We use a two-heap approach for optimal scheduling to determine which room hosts the most meetings. One min heap keeps
201+
track of available rooms, ordered by room number, while the other tracks rooms currently in use, ordered by their next
202+
availability (i.e., meeting end time). This setup ensures we always have quick access to the room that becomes free the
203+
earliest or the lowest-numbered available room.
204+
205+
As all meeting start times are unique, we sort the meetings by their start time to process them chronologically. For
206+
each meeting:
207+
208+
- **Freeing up rooms**: Before scheduling the current meeting, we remove entries from the in-use heap whose end times
209+
are less than or equal to the current meeting’s start time. Each freed room is pushed back into the available heap.
210+
- **Assigning a room**:
211+
- If the available heap is not empty, we pop the room with the lowest number and assign the meeting to it.
212+
- If no room is available, we pop the room from the in-use heap with the earliest end time. We delay the meeting to
213+
start at the room’s available time, then reassign it back into the in-use heap with the new end time.
214+
- **Tracking usage**: Each time a room is assigned, we increment its usage counter.
215+
216+
After processing all meetings, we scan the counters and return the room with the highest count. The room with the lowest
217+
number is selected in case of a tie.
218+
219+
The steps of the algorithm are as follows:
220+
1. Initialize a count array of size equal to the number of rooms to track the number of meetings held by each room.
221+
2. Create two min heaps:
222+
- `available`: Contains free room numbers.
223+
- `used_rooms`: Contains pairs of (end time, room number) for rooms currently in use.
224+
3. Populate the `available` min heap with all room numbers [0, 1, 2, ..., rooms-1].
225+
4. Sort the `meetings` list by their `start_time` to process them in chronological order.
226+
5. For each meeting interval `(start_time, end_time)`:
227+
- Free up rooms: While the top room in `used_rooms` has an `end_time``start_time`, remove it from `used_rooms` and
228+
add the room back to available.
229+
- If no rooms are available, pop the room that will be free soonest from `used_rooms`. As the meeting must be delayed,
230+
calculate its new `end_time` by extending the duration starting from the previous room’s `end_time`.
231+
- Allocate the smallest available room (pop from `available`) and push its new (`end_time`, `room`) into `used_rooms`.
232+
- Increment that room’s meeting count in the `count` array.
233+
6. After all meetings are processed, return the room’s index with the maximum meeting count. In case of a tie, return
234+
the smallest index.
235+
236+
![Solution 1](./images/solutions/meeting_rooms_3_solution_1.png)
237+
![Solution 2](./images/solutions/meeting_rooms_3_solution_2.png)
238+
![Solution 3](./images/solutions/meeting_rooms_3_solution_3.png)
239+
![Solution 4](./images/solutions/meeting_rooms_3_solution_4.png)
240+
![Solution 5](./images/solutions/meeting_rooms_3_solution_5.png)
241+
![Solution 6](./images/solutions/meeting_rooms_3_solution_6.png)
242+
![Solution 7](./images/solutions/meeting_rooms_3_solution_7.png)
243+
![Solution 8](./images/solutions/meeting_rooms_3_solution_8.png)
244+
![Solution 9](./images/solutions/meeting_rooms_3_solution_9.png)
245+
![Solution 10](./images/solutions/meeting_rooms_3_solution_10.png)
246+
![Solution 11](./images/solutions/meeting_rooms_3_solution_11.png)
247+
![Solution 12](./images/solutions/meeting_rooms_3_solution_12.png)
248+
![Solution 13](./images/solutions/meeting_rooms_3_solution_13.png)
249+
![Solution 14](./images/solutions/meeting_rooms_3_solution_14.png)
250+
![Solution 15](./images/solutions/meeting_rooms_3_solution_15.png)
251+
![Solution 16](./images/solutions/meeting_rooms_3_solution_16.png)
252+
253+
### Time Complexity
254+
255+
There are mainly two factors contributing toward the time complexity of a problem:
256+
257+
- **Sorting**: The sorting of the given intervals takes `O(m log(m))` where m is the number of meetings or intervals.
258+
- **Heap operations**: We perform multiple push and pop operations for each room. Therefore, we have `O(log n)` time
259+
complexity for each operation where n is the number of rooms. As the heap operations are done for all the given
260+
meetings, the time complexity leads to `O(m log(n))`.
261+
262+
Hence, the total complexity becomes `O(m log(m) + m log(n))`.
263+
264+
### Space Complexity
265+
266+
The space complexity of this solution will mainly be contributed by the `counter` array, i.e., O(n) where
267+
n is the number of rooms, and the two min-heaps `used_rooms` and `available` i.e., O(n+n)
268+
as in the worst-case scenario, either of these two heaps can have n elements. Hence, the overall space complexity will
269+
lead up to `O(n+n+n)`, which is `O(n)`.

algorithms/intervals/meeting_rooms/__init__.py

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import List
1+
from typing import List, Tuple
22
import heapq
33

44

@@ -68,3 +68,46 @@ def find_minimum_meeting_rooms_priority_queue(meetings: List[List[int]]) -> int:
6868

6969
# The size of the heap tells us the number of rooms allocated
7070
return len(rooms)
71+
72+
73+
def most_booked(meetings: List[List[int]], rooms: int) -> int:
74+
# A counter array that keeps track of the number of meetings held in each room
75+
counter = [0] * rooms
76+
# Min heap to keep track of free rooms
77+
available = [i for i in range(rooms)]
78+
# Min heap for rooms currently in use
79+
used_rooms: List[Tuple[int, int]] = []
80+
81+
# Sort the meetings by their start times to process them in chronological order. This uses additional space as we
82+
# don't want to mutate the input meetings list. In addition, incurs time complexity of O(n log(n)) due to sorting
83+
sorted_meetings = sorted(meetings, key=lambda x: x[0])
84+
85+
# For each meeting, free up rooms that have finished their meetings by moving them from used_rooms to available
86+
for meeting in sorted_meetings:
87+
current_start_time, current_end_time = meeting
88+
89+
# If a room is free, assign it to the current meeting. If no rooms are fre, delay the meeting until the earliest
90+
# available room is free, then schedule it
91+
92+
# Free up rooms that have finished their meetings by their current start time
93+
while used_rooms and used_rooms[0][0] <= current_start_time:
94+
used_room = heapq.heappop(used_rooms)
95+
end_time, room_number = used_room
96+
heapq.heappush(available, room_number)
97+
98+
# If no rooms are available, delay the meeting until a room becomes free
99+
if not available:
100+
used_room = heapq.heappop(used_rooms)
101+
used_room_end_time, used_room_number = used_room
102+
current_end_time = used_room_end_time + (
103+
current_end_time - current_start_time
104+
)
105+
heapq.heappush(available, used_room_number)
106+
107+
# Allocate the meeting to the available room with the lowest number
108+
available_room = heapq.heappop(available)
109+
heapq.heappush(used_rooms, (current_end_time, available_room))
110+
counter[available_room] += 1
111+
112+
# Once all meetings are processed, return the room with the highest number of meetings from the counter array
113+
return counter.index(max(counter))
45 KB
Loading
64.3 KB
Loading
66.6 KB
Loading
73.6 KB
Loading
25.5 KB
Loading
31.3 KB
Loading
38.1 KB
Loading

0 commit comments

Comments
 (0)