-
Notifications
You must be signed in to change notification settings - Fork 2
feat(algorithms, prefix-sum, intervals) employee free time #133
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
BrianLusina
merged 8 commits into
main
from
feat/algorithms-intervals-employee-free-time
Dec 30, 2025
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
021bc4b
feat(strings, palindrome): palindrom number
BrianLusina d95a9d2
feat(algorithms, prefix-sum): continuous sub array sum
BrianLusina abdbe3b
updating DIRECTORY.md
cbd6461
feat(algorithms, intervals): employee free time
BrianLusina e724b1a
updating DIRECTORY.md
c6c6442
fix(algorithms, prefix-sum, continuous-sub-array-sum): update cumulat…
BrianLusina 4b69e9e
refactor(algorithms, intervals, employee_free_time): input validation
BrianLusina 735682b
refactor(algorithms, intervals, employee-free-time): validate empty s…
BrianLusina File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| # Employee Free Time | ||
|
|
||
| You’re given a list containing the schedules of multiple employees. Each person’s schedule is a list of non-overlapping | ||
| intervals in sorted order. An interval is specified with the start and end time, both being positive integers. Your task | ||
| is to find the list of finite intervals representing the free time for all the employees. | ||
|
|
||
| > Note: The common free intervals are calculated between the earliest start time and the latest end time of all meetings | ||
| across all employees. | ||
|
|
||
| ## Constraints | ||
|
|
||
| - 1 <= `schedule.length`, `schedule[i].length` <= 50 | ||
| - 0 <= `interval.start`, `interval.end` <= 10^8, where `interval` is any interval in the list of schedules | ||
|
|
||
| ## Examples | ||
|
|
||
|  | ||
|  | ||
|  | ||
|
|
||
| ## Solution | ||
|
|
||
| The solution’s core revolves around merging overlapping intervals of employees and identifying the free time gaps | ||
| between these merged intervals. Using a min-heap, we arrange the intervals based on when they start, sorting them | ||
| according to their start times. When we pop an element from the min-heap, it guarantees that the earliest available | ||
| interval is returned for processing. As intervals are popped from the min-heap, the algorithm attempts to merge them. | ||
| If the currently popped interval’s start time exceeds the merged interval’s end time, a gap is identified, indicating a | ||
| free period. After identifying each gap, the algorithm restarts the merging process to continue identifying additional | ||
| periods of free time. | ||
|
|
||
| We use the following variables in our solution: | ||
|
|
||
| - `latest_end_time`: Stores the end time of the previously processed interval. | ||
| - `employee_index`: Stores the employee’s index value. | ||
| - `interval_index`: Stores the interval’s index of the employee, i. | ||
| - `free_time_slots`: Stores the free time intervals. | ||
|
|
||
| The steps of the algorithm are given below : | ||
| - We store the start time of each employee’s first interval, along with its index value and a value of 0, in a min-heap. | ||
| - We set previous to the start time of the first interval present in a heap. | ||
| - Then, we iterate a loop until the heap is empty, and in each iteration, we do the following: | ||
| - Pop an element from the min-heap and set `employee_index` and `interval_index` to the second and third values, | ||
| respectively, from the popped value. | ||
| - Select the interval from the input located at `employee_index`,`interval_index`. | ||
| - If the selected interval’s start time is greater than `latest_end_time`, it means that the time from `latest_end_time` | ||
| to the selected interval’s start time is free. So, add this interval to the `free_time_slots` array. | ||
| - Now, update the `latest_end_time` as `max(latest_end_time, end time of selected interval)`. | ||
| - If the current employee has any other interval, push it into the heap. | ||
| - After all the iterations, when the heap becomes empty, return the `free_time_slots` array. | ||
|
|
||
|  | ||
|  | ||
|  | ||
|  | ||
|  | ||
|  | ||
|  | ||
|
|
||
| ### Time Complexity | ||
|
|
||
| The time complexity of this algorithm is O(mlog(n)), where n is the number of employees and m is the total number of | ||
| intervals across all employees. This is because the time complexity of filling the heap is O(nlog(n)) and the time | ||
| complexity of processing the heap is O(mlog(n)). | ||
|
|
||
| ### Space Complexity | ||
|
|
||
| We use a heap in the solution, which can have a maximum of n elements. Hence, the space complexity of this solution is | ||
| O(n), where n is the number of employees. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,122 @@ | ||
| from typing import List, Tuple | ||
| import heapq | ||
| from algorithms.intervals.employee_free_time.interval import Interval | ||
|
|
||
|
|
||
| def employee_free_time(schedules: List[List[Interval]]) -> List[Interval]: | ||
| """ | ||
| Finds intervals where employees have free time | ||
|
|
||
| Complexity: | ||
| Time Complexity: O(NlogN), where N is the total number of intervals across all employees. This is dominated by the sorting step. | ||
| Space Complexity: O(N) to store the flattened all_schedules list. | ||
|
|
||
| Args: | ||
| schedules(list): a list of lists, where each inner list contains `Interval` objects representing an employee's schedule. | ||
| Returns: | ||
| list: Intervals of employee free time | ||
| """ | ||
| # We need to combine and merge all employee intervals(schedules) into one unified schedule to be able to check | ||
| # for free time. Since using a new list incurs a space cost of O(n), we can modify the input list, however, this | ||
| # has side effects if the schedule list is used in other parts of the program. To avoid side effects, we copy over | ||
| # the input list into a new list to handle the merging | ||
| all_schedules: List[Interval] = [] | ||
| for schedule in schedules: | ||
| for schedule_interval in schedule: | ||
| all_schedules.append(schedule_interval) | ||
|
|
||
| if not all_schedules: | ||
| return [] | ||
|
|
||
| # sort by start time | ||
| all_schedules.sort(key=lambda x: x.start) | ||
|
|
||
| # Keep track of the latest meeting's end time. This is initialized the first schedule's end time | ||
| latest_end = all_schedules[0].end | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| # This will keep track of the free time slots | ||
| free_time_slots: List[Interval] = [] | ||
|
|
||
| # Now we need to find the gaps in the combined schedule. Since we have already used the first schedule's end time | ||
| # as the latest end we have seen so far, we start at the next interval. | ||
| for idx in range(1, len(all_schedules)): | ||
| current_schedule = all_schedules[idx] | ||
| current_interval_start, current_interval_end = ( | ||
| current_schedule.start, | ||
| current_schedule.end, | ||
| ) | ||
|
|
||
| # If the current interval's start time is greater than the latest end time we have seen so far, it means we have | ||
| # found free time | ||
| if current_interval_start > latest_end: | ||
| # we have a free time slot | ||
| free_interval = Interval(start=latest_end, end=current_interval_start) | ||
| free_time_slots.append(free_interval) | ||
|
|
||
| # update the latest_end to the maximum of the latest end time seen so far | ||
| latest_end = max(latest_end, current_interval_end) | ||
|
|
||
| return free_time_slots | ||
|
|
||
|
|
||
| def employee_free_time_heap(schedules: List[List[Interval]]) -> List[Interval]: | ||
| """ | ||
| Finds intervals where employees have free time using a min heap | ||
|
|
||
| Args: | ||
| schedules(list): a list of lists, where each inner list contains `Interval` objects representing an employee's schedule. | ||
| Returns: | ||
| list: Intervals of employee free time | ||
| """ | ||
| heap: List[Tuple[int, int, int]] = [] | ||
| # Iterate for all employees' schedules | ||
| # and add start of each schedule's first interval along with | ||
| # its index value and a value 0. | ||
| for i in range(len(schedules)): | ||
| if schedules[i]: # Only add if employee has at least one interval | ||
| heap.append((schedules[i][0].start, i, 0)) | ||
|
|
||
| # Create heap from array elements. | ||
| heapq.heapify(heap) | ||
|
|
||
| # Handle empty heap | ||
| if not heap: | ||
| return [] | ||
|
|
||
| # Take an empty array to store results. | ||
| free_time_slots = [] | ||
|
|
||
| # Set 'latest_end_time' to the start time of first interval in heap. | ||
| latest_end_time = schedules[heap[0][1]][heap[0][2]].end | ||
BrianLusina marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| # Iterate till heap is empty | ||
| while heap: | ||
| # Pop an element from heap and set value of employee_index and interval_index | ||
| _, employee_index, interval_index = heapq.heappop(heap) | ||
|
|
||
| # Select an interval | ||
| schedule_interval = schedules[employee_index][interval_index] | ||
|
|
||
| # If selected interval's start value is greater than the | ||
| # latest_end_time value, it means that this interval is free. | ||
| # So, add this interval (latest_end_time, interval's end value) into result. | ||
| if schedule_interval.start > latest_end_time: | ||
| free_time_slots.append(Interval(latest_end_time, schedule_interval.start)) | ||
|
|
||
| # Update the latest_end_time as maximum of latest_end_time and interval's end value. | ||
| latest_end_time = max(latest_end_time, schedule_interval.end) | ||
|
|
||
| # If there is another interval in current employees' schedule, | ||
| # push that into heap. | ||
| if interval_index + 1 < len(schedules[employee_index]): | ||
| heapq.heappush( | ||
| heap, | ||
| ( | ||
| schedules[employee_index][interval_index + 1].start, | ||
| employee_index, | ||
| interval_index + 1, | ||
| ), | ||
| ) | ||
|
|
||
| # When the heap is empty, return result. | ||
| return free_time_slots | ||
Binary file added
BIN
+98.2 KB
...s/intervals/employee_free_time/images/examples/employee_free_time_example_1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+81.7 KB
...s/intervals/employee_free_time/images/examples/employee_free_time_example_2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+73.1 KB
...s/intervals/employee_free_time/images/examples/employee_free_time_example_3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+109 KB
...vals/employee_free_time/images/solutions/employee_free_time_heap_solution_1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+105 KB
...vals/employee_free_time/images/solutions/employee_free_time_heap_solution_2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+140 KB
...vals/employee_free_time/images/solutions/employee_free_time_heap_solution_3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+142 KB
...vals/employee_free_time/images/solutions/employee_free_time_heap_solution_4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+126 KB
...vals/employee_free_time/images/solutions/employee_free_time_heap_solution_5.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+120 KB
...vals/employee_free_time/images/solutions/employee_free_time_heap_solution_6.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+59.3 KB
...vals/employee_free_time/images/solutions/employee_free_time_heap_solution_7.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| class Interval: | ||
| def __init__(self, start: int, end: int): | ||
| self.start = start | ||
| self.end = end | ||
| self.closed = True # by default, the interval is closed | ||
|
|
||
| def set_closed(self, closed: bool) -> None: | ||
| # set the flag for closed/open | ||
| self.closed = closed | ||
|
|
||
| def __str__(self): | ||
| return ( | ||
| "[" + str(self.start) + ", " + str(self.end) + "]" | ||
| if self.closed | ||
| else "(" + str(self.start) + ", " + str(self.end) + ")" | ||
| ) | ||
|
|
||
| def __eq__(self, other: "Interval") -> bool: | ||
| return self.start == other.start and self.end == other.end |
105 changes: 105 additions & 0 deletions
105
algorithms/intervals/employee_free_time/test_employee_free_time.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,105 @@ | ||
| import unittest | ||
| from typing import List | ||
| from parameterized import parameterized | ||
| from algorithms.intervals.employee_free_time.interval import Interval | ||
| from algorithms.intervals.employee_free_time import ( | ||
| employee_free_time, | ||
| employee_free_time_heap, | ||
| ) | ||
|
|
||
| EMPLOYEE_FREE_TIME_TEST_CASES = [ | ||
| ( | ||
| [ | ||
| [Interval(start=1, end=2), Interval(start=5, end=6)], | ||
| [Interval(start=1, end=3)], | ||
| [Interval(start=4, end=10)], | ||
| ], | ||
| [Interval(start=3, end=4)], | ||
| ), | ||
| ( | ||
| [ | ||
| [Interval(start=1, end=3), Interval(start=6, end=7)], | ||
| [Interval(start=2, end=4)], | ||
| [Interval(start=2, end=5), Interval(start=9, end=12)], | ||
| ], | ||
| [Interval(start=5, end=6), Interval(start=7, end=9)], | ||
| ), | ||
| ( | ||
| [ | ||
| [Interval(start=2, end=3), Interval(start=7, end=9)], | ||
| [Interval(start=1, end=4), Interval(start=6, end=7)], | ||
| ], | ||
| [Interval(start=4, end=6)], | ||
| ), | ||
| ( | ||
| [ | ||
| [Interval(start=3, end=5), Interval(start=8, end=10)], | ||
| [Interval(start=4, end=6), Interval(start=9, end=12)], | ||
| [Interval(start=5, end=6), Interval(start=8, end=10)], | ||
| ], | ||
| [Interval(start=6, end=8)], | ||
| ), | ||
| ( | ||
| [ | ||
| [ | ||
| Interval(start=1, end=2), | ||
| Interval(start=3, end=4), | ||
| Interval(start=5, end=6), | ||
| Interval(start=7, end=8), | ||
| Interval(start=9, end=10), | ||
| Interval(start=11, end=12), | ||
| ], | ||
| [ | ||
| Interval(start=1, end=2), | ||
| Interval(start=3, end=4), | ||
| Interval(start=5, end=6), | ||
| Interval(start=7, end=8), | ||
| Interval(start=9, end=10), | ||
| Interval(start=11, end=12), | ||
| ], | ||
| [ | ||
| Interval(start=1, end=2), | ||
| Interval(start=3, end=4), | ||
| Interval(start=5, end=6), | ||
| Interval(start=7, end=8), | ||
| Interval(start=9, end=10), | ||
| Interval(start=11, end=12), | ||
| ], | ||
| [ | ||
| Interval(start=1, end=2), | ||
| Interval(start=3, end=4), | ||
| Interval(start=5, end=6), | ||
| Interval(start=7, end=8), | ||
| Interval(start=9, end=10), | ||
| Interval(start=11, end=12), | ||
| ], | ||
| ], | ||
| [ | ||
| Interval(start=2, end=3), | ||
| Interval(start=4, end=5), | ||
| Interval(start=6, end=7), | ||
| Interval(start=8, end=9), | ||
| Interval(start=10, end=11), | ||
| ], | ||
| ), | ||
| ] | ||
|
|
||
|
|
||
| class EmployeeFreeTimeTestCase(unittest.TestCase): | ||
| @parameterized.expand(EMPLOYEE_FREE_TIME_TEST_CASES) | ||
| def test_employee_free_time( | ||
| self, schedule: List[List[Interval]], expected: List[Interval] | ||
| ): | ||
| actual = employee_free_time(schedule) | ||
| self.assertListEqual(expected, actual) | ||
|
|
||
| @parameterized.expand(EMPLOYEE_FREE_TIME_TEST_CASES) | ||
| def test_employee_free_time_heap( | ||
| self, schedule: List[List[Interval]], expected: List[Interval] | ||
| ): | ||
| actual = employee_free_time_heap(schedule) | ||
| self.assertListEqual(expected, actual) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| unittest.main() |
Empty file.
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.