diff --git a/DIRECTORY.md b/DIRECTORY.md index 1350cd36..701993c6 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -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 @@ -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) @@ -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 @@ -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 @@ -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) diff --git a/algorithms/intervals/meeting_rooms/README.md b/algorithms/intervals/meeting_rooms/README.md index dc459184..561adaa4 100644 --- a/algorithms/intervals/meeting_rooms/README.md +++ b/algorithms/intervals/meeting_rooms/README.md @@ -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)`. diff --git a/algorithms/intervals/meeting_rooms/__init__.py b/algorithms/intervals/meeting_rooms/__init__.py index aefe9314..39650664 100644 --- a/algorithms/intervals/meeting_rooms/__init__.py +++ b/algorithms/intervals/meeting_rooms/__init__.py @@ -1,4 +1,4 @@ -from typing import List +from typing import List, Tuple import heapq @@ -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)) diff --git a/algorithms/intervals/meeting_rooms/images/examples/meeting_rooms_3_example_1.png b/algorithms/intervals/meeting_rooms/images/examples/meeting_rooms_3_example_1.png new file mode 100644 index 00000000..e6b986aa Binary files /dev/null and b/algorithms/intervals/meeting_rooms/images/examples/meeting_rooms_3_example_1.png differ diff --git a/algorithms/intervals/meeting_rooms/images/examples/meeting_rooms_3_example_2.png b/algorithms/intervals/meeting_rooms/images/examples/meeting_rooms_3_example_2.png new file mode 100644 index 00000000..6237aab7 Binary files /dev/null and b/algorithms/intervals/meeting_rooms/images/examples/meeting_rooms_3_example_2.png differ diff --git a/algorithms/intervals/meeting_rooms/images/examples/meeting_rooms_3_example_3.png b/algorithms/intervals/meeting_rooms/images/examples/meeting_rooms_3_example_3.png new file mode 100644 index 00000000..4a8b3833 Binary files /dev/null and b/algorithms/intervals/meeting_rooms/images/examples/meeting_rooms_3_example_3.png differ diff --git a/algorithms/intervals/meeting_rooms/images/examples/meeting_rooms_3_example_4.png b/algorithms/intervals/meeting_rooms/images/examples/meeting_rooms_3_example_4.png new file mode 100644 index 00000000..3be2cab4 Binary files /dev/null and b/algorithms/intervals/meeting_rooms/images/examples/meeting_rooms_3_example_4.png differ diff --git a/algorithms/intervals/meeting_rooms/images/solutions/meeting_rooms_3_solution_1.png b/algorithms/intervals/meeting_rooms/images/solutions/meeting_rooms_3_solution_1.png new file mode 100644 index 00000000..6161a4df Binary files /dev/null and b/algorithms/intervals/meeting_rooms/images/solutions/meeting_rooms_3_solution_1.png differ diff --git a/algorithms/intervals/meeting_rooms/images/solutions/meeting_rooms_3_solution_10.png b/algorithms/intervals/meeting_rooms/images/solutions/meeting_rooms_3_solution_10.png new file mode 100644 index 00000000..86092ad1 Binary files /dev/null and b/algorithms/intervals/meeting_rooms/images/solutions/meeting_rooms_3_solution_10.png differ diff --git a/algorithms/intervals/meeting_rooms/images/solutions/meeting_rooms_3_solution_11.png b/algorithms/intervals/meeting_rooms/images/solutions/meeting_rooms_3_solution_11.png new file mode 100644 index 00000000..cfa3531f Binary files /dev/null and b/algorithms/intervals/meeting_rooms/images/solutions/meeting_rooms_3_solution_11.png differ diff --git a/algorithms/intervals/meeting_rooms/images/solutions/meeting_rooms_3_solution_12.png b/algorithms/intervals/meeting_rooms/images/solutions/meeting_rooms_3_solution_12.png new file mode 100644 index 00000000..d23743c0 Binary files /dev/null and b/algorithms/intervals/meeting_rooms/images/solutions/meeting_rooms_3_solution_12.png differ diff --git a/algorithms/intervals/meeting_rooms/images/solutions/meeting_rooms_3_solution_13.png b/algorithms/intervals/meeting_rooms/images/solutions/meeting_rooms_3_solution_13.png new file mode 100644 index 00000000..73a6a3d3 Binary files /dev/null and b/algorithms/intervals/meeting_rooms/images/solutions/meeting_rooms_3_solution_13.png differ diff --git a/algorithms/intervals/meeting_rooms/images/solutions/meeting_rooms_3_solution_14.png b/algorithms/intervals/meeting_rooms/images/solutions/meeting_rooms_3_solution_14.png new file mode 100644 index 00000000..7438a123 Binary files /dev/null and b/algorithms/intervals/meeting_rooms/images/solutions/meeting_rooms_3_solution_14.png differ diff --git a/algorithms/intervals/meeting_rooms/images/solutions/meeting_rooms_3_solution_15.png b/algorithms/intervals/meeting_rooms/images/solutions/meeting_rooms_3_solution_15.png new file mode 100644 index 00000000..6c222907 Binary files /dev/null and b/algorithms/intervals/meeting_rooms/images/solutions/meeting_rooms_3_solution_15.png differ diff --git a/algorithms/intervals/meeting_rooms/images/solutions/meeting_rooms_3_solution_16.png b/algorithms/intervals/meeting_rooms/images/solutions/meeting_rooms_3_solution_16.png new file mode 100644 index 00000000..7dc4e138 Binary files /dev/null and b/algorithms/intervals/meeting_rooms/images/solutions/meeting_rooms_3_solution_16.png differ diff --git a/algorithms/intervals/meeting_rooms/images/solutions/meeting_rooms_3_solution_2.png b/algorithms/intervals/meeting_rooms/images/solutions/meeting_rooms_3_solution_2.png new file mode 100644 index 00000000..9c84f981 Binary files /dev/null and b/algorithms/intervals/meeting_rooms/images/solutions/meeting_rooms_3_solution_2.png differ diff --git a/algorithms/intervals/meeting_rooms/images/solutions/meeting_rooms_3_solution_3.png b/algorithms/intervals/meeting_rooms/images/solutions/meeting_rooms_3_solution_3.png new file mode 100644 index 00000000..214f3b70 Binary files /dev/null and b/algorithms/intervals/meeting_rooms/images/solutions/meeting_rooms_3_solution_3.png differ diff --git a/algorithms/intervals/meeting_rooms/images/solutions/meeting_rooms_3_solution_4.png b/algorithms/intervals/meeting_rooms/images/solutions/meeting_rooms_3_solution_4.png new file mode 100644 index 00000000..fc877cbd Binary files /dev/null and b/algorithms/intervals/meeting_rooms/images/solutions/meeting_rooms_3_solution_4.png differ diff --git a/algorithms/intervals/meeting_rooms/images/solutions/meeting_rooms_3_solution_5.png b/algorithms/intervals/meeting_rooms/images/solutions/meeting_rooms_3_solution_5.png new file mode 100644 index 00000000..802650b6 Binary files /dev/null and b/algorithms/intervals/meeting_rooms/images/solutions/meeting_rooms_3_solution_5.png differ diff --git a/algorithms/intervals/meeting_rooms/images/solutions/meeting_rooms_3_solution_6.png b/algorithms/intervals/meeting_rooms/images/solutions/meeting_rooms_3_solution_6.png new file mode 100644 index 00000000..9abccc03 Binary files /dev/null and b/algorithms/intervals/meeting_rooms/images/solutions/meeting_rooms_3_solution_6.png differ diff --git a/algorithms/intervals/meeting_rooms/images/solutions/meeting_rooms_3_solution_7.png b/algorithms/intervals/meeting_rooms/images/solutions/meeting_rooms_3_solution_7.png new file mode 100644 index 00000000..2c302e1f Binary files /dev/null and b/algorithms/intervals/meeting_rooms/images/solutions/meeting_rooms_3_solution_7.png differ diff --git a/algorithms/intervals/meeting_rooms/images/solutions/meeting_rooms_3_solution_8.png b/algorithms/intervals/meeting_rooms/images/solutions/meeting_rooms_3_solution_8.png new file mode 100644 index 00000000..beb7edec Binary files /dev/null and b/algorithms/intervals/meeting_rooms/images/solutions/meeting_rooms_3_solution_8.png differ diff --git a/algorithms/intervals/meeting_rooms/images/solutions/meeting_rooms_3_solution_9.png b/algorithms/intervals/meeting_rooms/images/solutions/meeting_rooms_3_solution_9.png new file mode 100644 index 00000000..831e1261 Binary files /dev/null and b/algorithms/intervals/meeting_rooms/images/solutions/meeting_rooms_3_solution_9.png differ diff --git a/algorithms/intervals/meeting_rooms/test_most_booked_meeting_rooms.py b/algorithms/intervals/meeting_rooms/test_most_booked_meeting_rooms.py new file mode 100644 index 00000000..56c900dc --- /dev/null +++ b/algorithms/intervals/meeting_rooms/test_most_booked_meeting_rooms.py @@ -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()