Skip to content
Merged
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
8 changes: 7 additions & 1 deletion DIRECTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@
* [Test Intervals Intersection](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/intervals/interval_intersection/test_intervals_intersection.py)
* Meeting Rooms
* [Test Min Meeting Rooms](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/intervals/meeting_rooms/test_min_meeting_rooms.py)
* [Test Most Booked Meeting Rooms](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/intervals/meeting_rooms/test_most_booked_meeting_rooms.py)
* Merge Intervals
* [Test Merge Intervals](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/intervals/merge_intervals/test_merge_intervals.py)
* Non Overlapping Intervals
Expand Down Expand Up @@ -451,9 +452,11 @@
* [Test My Hashset](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/hashset/test_my_hashset.py)
* Linked Lists
* Circular
* [Circular Linked List Utils](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/linked_lists/circular/circular_linked_list_utils.py)
* [Node](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/linked_lists/circular/node.py)
* [Test Circular Linked List](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/linked_lists/circular/test_circular_linked_list.py)
* [Test Circular Linked List Split](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/linked_lists/circular/test_circular_linked_list_split.py)
* [Test Circular Linked List Utils](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/linked_lists/circular/test_circular_linked_list_utils.py)
* Doubly Linked List
* [Node](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/linked_lists/doubly_linked_list/node.py)
* [Test Doubly Linked List](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/linked_lists/doubly_linked_list/test_doubly_linked_list.py)
Expand Down Expand Up @@ -911,6 +914,8 @@
* [Test Reverse Integer](https://github.com/BrianLusina/PythonSnips/blob/master/pymath/reverse_integer/test_reverse_integer.py)
* Self Crossing
* [Test Is Self Crossing](https://github.com/BrianLusina/PythonSnips/blob/master/pymath/self_crossing/test_is_self_crossing.py)
* Sum Of Multiples
* [Test Sum Of Multiples](https://github.com/BrianLusina/PythonSnips/blob/master/pymath/sum_of_multiples/test_sum_of_multiples.py)
* Super Size
* [Test Super Size](https://github.com/BrianLusina/PythonSnips/blob/master/pymath/super_size/test_super_size.py)
* Triangles
Expand Down Expand Up @@ -942,6 +947,8 @@
* [Test First Occurrence](https://github.com/BrianLusina/PythonSnips/blob/master/pystrings/first_occurrence/test_first_occurrence.py)
* Greatest Common Divisor
* [Test Greatest Common Divisor](https://github.com/BrianLusina/PythonSnips/blob/master/pystrings/greatest_common_divisor/test_greatest_common_divisor.py)
* Integer To English
* [Test Integer To English](https://github.com/BrianLusina/PythonSnips/blob/master/pystrings/integer_to_english/test_integer_to_english.py)
* Inttostr
* [Test Int To Str](https://github.com/BrianLusina/PythonSnips/blob/master/pystrings/inttostr/test_int_to_str.py)
* Is Prefix
Expand Down Expand Up @@ -1207,7 +1214,6 @@
* [Test Sieve Of Erastothenes](https://github.com/BrianLusina/PythonSnips/blob/master/tests/pymath/test_sieve_of_erastothenes.py)
* [Test Split Even Numbers](https://github.com/BrianLusina/PythonSnips/blob/master/tests/pymath/test_split_even_numbers.py)
* [Test Sum Between](https://github.com/BrianLusina/PythonSnips/blob/master/tests/pymath/test_sum_between.py)
* [Test Sum Of Multiples](https://github.com/BrianLusina/PythonSnips/blob/master/tests/pymath/test_sum_of_multiples.py)
* [Test Sum Same](https://github.com/BrianLusina/PythonSnips/blob/master/tests/pymath/test_sum_same.py)
* [Test Summation](https://github.com/BrianLusina/PythonSnips/blob/master/tests/pymath/test_summation.py)
* [Test Tank Truck](https://github.com/BrianLusina/PythonSnips/blob/master/tests/pymath/test_tank_truck.py)
Expand Down
153 changes: 153 additions & 0 deletions algorithms/intervals/meeting_rooms/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,156 @@ becomes O(n log(n)) + O(n log(n)) = O(n log(n)).

The space complexity of this solution is O(n). This is because we are building a min-heap that, in the worst case,
can have all the input elements. Therefore, the space required to compute this solution would be O(n).

---
# Meeting Rooms III

Given an integer, rooms, which represents the total number of rooms, where each room is numbered from 0 to rooms - 1.
Additionally, you are given a 2D integer array called meetings, where each element meetings[i] =
[starti, endi] indicates that a meeting will be held in the half-closed interval [starti, endi). Each starti is unique.

Meetings are allocated to rooms in the following manner:

1. Each meeting will take place in the unused room with the lowest number.
2. If there are no available rooms, the meeting will be delayed until a room becomes free. The delayed meeting should
have the same duration as the original meeting.
3. When a room is vacated, the meeting with the earliest original start time is given priority for that room.

Your task is to determine the room number that hosted the most meetings. If there are multiple rooms, return the room
with the lowest number.

> Note: A half-closed interval [a, b) is the interval between a and b, including a and not including b.

## Constraints

- 1 ≤ rooms ≤ 100
- 1 ≤ meetings.length ≤ 1000
- meetings[i].length == 2
- 0 ≤ starti < endi ≤ 10000
- All the values of `starti` are unique

## Examples

![Example 1](./images/examples/meeting_rooms_3_example_1.png)
![Example 2](./images/examples/meeting_rooms_3_example_3.png)
![Example 3](./images/examples/meeting_rooms_3_example_3.png)
![Example 4](./images/examples/meeting_rooms_3_example_4.png)

Example 5:
```text
Input: n = 2, meetings = [[0,10],[1,5],[2,7],[3,4]]
Output: 0
Explanation:
- At time 0, both rooms are not being used. The first meeting starts in room 0.
- At time 1, only room 1 is not being used. The second meeting starts in room 1.
- At time 2, both rooms are being used. The third meeting is delayed.
- At time 3, both rooms are being used. The fourth meeting is delayed.
- At time 5, the meeting in room 1 finishes. The third meeting starts in room 1 for the time period [5,10).
- At time 10, the meetings in both rooms finish. The fourth meeting starts in room 0 for the time period [10,11).
Both rooms 0 and 1 held 2 meetings, so we return 0.
```

Example 6:
```text
Input: n = 3, meetings = [[1,20],[2,10],[3,5],[4,9],[6,8]]
Output: 1
Explanation:
- At time 1, all three rooms are not being used. The first meeting starts in room 0.
- At time 2, rooms 1 and 2 are not being used. The second meeting starts in room 1.
- At time 3, only room 2 is not being used. The third meeting starts in room 2.
- At time 4, all three rooms are being used. The fourth meeting is delayed.
- At time 5, the meeting in room 2 finishes. The fourth meeting starts in room 2 for the time period [5,10).
- At time 6, all three rooms are being used. The fifth meeting is delayed.
- At time 10, the meetings in rooms 1 and 2 finish. The fifth meeting starts in room 1 for the time period [10,12).
Room 0 held 1 meeting while rooms 1 and 2 each held 2 meetings, so we return 1.
```

## Topics

- Array
- Hash Table
- Sorting
- Heap (Priority Queue)
- Simulation

## Hints

- Sort meetings based on start times.
- Use two min heaps, the first one keeps track of the numbers of all the rooms that are free. The second heap keeps
track of the end times of all the meetings that are happening and the room that they are in.
- Keep track of the number of times each room is used in an array.
- With each meeting, check if there are any free rooms. If there are, then use the room with the smallest number.
Otherwise, assign the meeting to the room whose meeting will end the soonest.

## Solution

We use a two-heap approach for optimal scheduling to determine which room hosts the most meetings. One min heap keeps
track of available rooms, ordered by room number, while the other tracks rooms currently in use, ordered by their next
availability (i.e., meeting end time). This setup ensures we always have quick access to the room that becomes free the
earliest or the lowest-numbered available room.

As all meeting start times are unique, we sort the meetings by their start time to process them chronologically. For
each meeting:

- **Freeing up rooms**: Before scheduling the current meeting, we remove entries from the in-use heap whose end times
are less than or equal to the current meeting’s start time. Each freed room is pushed back into the available heap.
- **Assigning a room**:
- If the available heap is not empty, we pop the room with the lowest number and assign the meeting to it.
- If no room is available, we pop the room from the in-use heap with the earliest end time. We delay the meeting to
start at the room’s available time, then reassign it back into the in-use heap with the new end time.
- **Tracking usage**: Each time a room is assigned, we increment its usage counter.

After processing all meetings, we scan the counters and return the room with the highest count. The room with the lowest
number is selected in case of a tie.

The steps of the algorithm are as follows:
1. Initialize a count array of size equal to the number of rooms to track the number of meetings held by each room.
2. Create two min heaps:
- `available`: Contains free room numbers.
- `used_rooms`: Contains pairs of (end time, room number) for rooms currently in use.
3. Populate the `available` min heap with all room numbers [0, 1, 2, ..., rooms-1].
4. Sort the `meetings` list by their `start_time` to process them in chronological order.
5. For each meeting interval `(start_time, end_time)`:
- Free up rooms: While the top room in `used_rooms` has an `end_time` ≤ `start_time`, remove it from `used_rooms` and
add the room back to available.
- If no rooms are available, pop the room that will be free soonest from `used_rooms`. As the meeting must be delayed,
calculate its new `end_time` by extending the duration starting from the previous room’s `end_time`.
- Allocate the smallest available room (pop from `available`) and push its new (`end_time`, `room`) into `used_rooms`.
- Increment that room’s meeting count in the `count` array.
6. After all meetings are processed, return the room’s index with the maximum meeting count. In case of a tie, return
the smallest index.

![Solution 1](./images/solutions/meeting_rooms_3_solution_1.png)
![Solution 2](./images/solutions/meeting_rooms_3_solution_2.png)
![Solution 3](./images/solutions/meeting_rooms_3_solution_3.png)
![Solution 4](./images/solutions/meeting_rooms_3_solution_4.png)
![Solution 5](./images/solutions/meeting_rooms_3_solution_5.png)
![Solution 6](./images/solutions/meeting_rooms_3_solution_6.png)
![Solution 7](./images/solutions/meeting_rooms_3_solution_7.png)
![Solution 8](./images/solutions/meeting_rooms_3_solution_8.png)
![Solution 9](./images/solutions/meeting_rooms_3_solution_9.png)
![Solution 10](./images/solutions/meeting_rooms_3_solution_10.png)
![Solution 11](./images/solutions/meeting_rooms_3_solution_11.png)
![Solution 12](./images/solutions/meeting_rooms_3_solution_12.png)
![Solution 13](./images/solutions/meeting_rooms_3_solution_13.png)
![Solution 14](./images/solutions/meeting_rooms_3_solution_14.png)
![Solution 15](./images/solutions/meeting_rooms_3_solution_15.png)
![Solution 16](./images/solutions/meeting_rooms_3_solution_16.png)

### Time Complexity

There are mainly two factors contributing toward the time complexity of a problem:

- **Sorting**: The sorting of the given intervals takes `O(m log(m))` where m is the number of meetings or intervals.
- **Heap operations**: We perform multiple push and pop operations for each room. Therefore, we have `O(log n)` time
complexity for each operation where n is the number of rooms. As the heap operations are done for all the given
meetings, the time complexity leads to `O(m log(n))`.

Hence, the total complexity becomes `O(m log(m) + m log(n))`.

### Space Complexity

The space complexity of this solution will mainly be contributed by the `counter` array, i.e., O(n) where
n is the number of rooms, and the two min-heaps `used_rooms` and `available` i.e., O(n+n)
as in the worst-case scenario, either of these two heaps can have n elements. Hence, the overall space complexity will
lead up to `O(n+n+n)`, which is `O(n)`.
45 changes: 44 additions & 1 deletion algorithms/intervals/meeting_rooms/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import List
from typing import List, Tuple
import heapq


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

# The size of the heap tells us the number of rooms allocated
return len(rooms)


def most_booked(meetings: List[List[int]], rooms: int) -> int:
# A counter array that keeps track of the number of meetings held in each room
counter = [0] * rooms
# Min heap to keep track of free rooms
available = [i for i in range(rooms)]
# Min heap for rooms currently in use
used_rooms: List[Tuple[int, int]] = []

# Sort the meetings by their start times to process them in chronological order. This uses additional space as we
# don't want to mutate the input meetings list. In addition, incurs time complexity of O(n log(n)) due to sorting
sorted_meetings = sorted(meetings, key=lambda x: x[0])

# For each meeting, free up rooms that have finished their meetings by moving them from used_rooms to available
for meeting in sorted_meetings:
current_start_time, current_end_time = meeting

# If a room is free, assign it to the current meeting. If no rooms are fre, delay the meeting until the earliest
# available room is free, then schedule it

# Free up rooms that have finished their meetings by their current start time
while used_rooms and used_rooms[0][0] <= current_start_time:
used_room = heapq.heappop(used_rooms)
end_time, room_number = used_room
heapq.heappush(available, room_number)

# If no rooms are available, delay the meeting until a room becomes free
if not available:
used_room = heapq.heappop(used_rooms)
used_room_end_time, used_room_number = used_room
current_end_time = used_room_end_time + (
current_end_time - current_start_time
)
heapq.heappush(available, used_room_number)

# Allocate the meeting to the available room with the lowest number
available_room = heapq.heappop(available)
heapq.heappush(used_rooms, (current_end_time, available_room))
counter[available_room] += 1

# Once all meetings are processed, return the room with the highest number of meetings from the counter array
return counter.index(max(counter))
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import unittest
from typing import List
from parameterized import parameterized
from algorithms.intervals.meeting_rooms import most_booked

MOST_BOOKED_MEETING_ROOMS_TEST_CASES = [
([[0, 5], [1, 6], [6, 7], [7, 8], [8, 9]], 2, 0),
([[0, 10], [1, 11], [2, 12], [3, 13], [4, 14], [5, 15]], 3, 0),
([[0, 9], [1, 2], [2, 3], [3, 4], [5, 6], [6, 7], [7, 8], [8, 9]], 3, 1),
([[0, 1], [1, 2], [2, 3], [3, 4], [4, 5]], 1, 0),
([[0, 4], [1, 3], [2, 4], [3, 5], [4, 6], [5, 7]], 4, 1),
([[0, 10], [1, 5], [2, 7], [3, 4]], 2, 0),
([[1, 20], [2, 10], [3, 5], [4, 9], [6, 8]], 3, 1),
]


class MostBookedMeetingRoomsTestCase(unittest.TestCase):
@parameterized.expand(MOST_BOOKED_MEETING_ROOMS_TEST_CASES)
def test_most_booked(self, meetings: List[List[int]], rooms: int, expected: int):
actual = most_booked(meetings, rooms)
self.assertEqual(expected, actual)


if __name__ == "__main__":
unittest.main()
Loading