Skip to content

Commit cbd6461

Browse files
committed
feat(algorithms, intervals): employee free time
1 parent abdbe3b commit cbd6461

14 files changed

+306
-0
lines changed
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# Employee Free Time
2+
3+
You’re given a list containing the schedules of multiple employees. Each person’s schedule is a list of non-overlapping
4+
intervals in sorted order. An interval is specified with the start and end time, both being positive integers. Your task
5+
is to find the list of finite intervals representing the free time for all the employees.
6+
7+
> Note: The common free intervals are calculated between the earliest start time and the latest end time of all meetings
8+
across all employees.
9+
10+
## Constraints
11+
12+
- 1 <= `schedule.length`, `schedule[i].length` <= 50
13+
- 0 <= `interval.start`, `interval.end` <= 10^8, where `interval` is any interval in the list of schedules
14+
15+
## Examples
16+
17+
![Example 1](./images/examples/employee_free_time_example_1.png)
18+
![Example 2](./images/examples/employee_free_time_example_2.png)
19+
![Example 3](./images/examples/employee_free_time_example_3.png)
20+
21+
## Solution
22+
23+
The solution’s core revolves around merging overlapping intervals of employees and identifying the free time gaps
24+
between these merged intervals. Using a min-heap, we arrange the intervals based on when they start, sorting them
25+
according to their start times. When we pop an element from the min-heap, it guarantees that the earliest available
26+
interval is returned for processing. As intervals are popped from the min-heap, the algorithm attempts to merge them.
27+
If the currently popped interval’s start time exceeds the merged interval’s end time, a gap is identified, indicating a
28+
free period. After identifying each gap, the algorithm restarts the merging process to continue identifying additional
29+
periods of free time.
30+
31+
We use the following variables in our solution:
32+
33+
- `latest_end_time`: Stores the end time of the previously processed interval.
34+
- `employee_index`: Stores the employee’s index value.
35+
- `interval_index`: Stores the interval’s index of the employee, i.
36+
- `free_time_slots`: Stores the free time intervals.
37+
38+
The steps of the algorithm are given below :
39+
- 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.
40+
- We set previous to the start time of the first interval present in a heap.
41+
- Then, we iterate a loop until the heap is empty, and in each iteration, we do the following:
42+
- Pop an element from the min-heap and set `employee_index` and `interval_index` to the second and third values,
43+
respectively, from the popped value.
44+
- Select the interval from the input located at `employee_index`,`interval_index`.
45+
- If the selected interval’s start time is greater than `latest_end_time`, it means that the time from `latest_end_time`
46+
to the selected interval’s start time is free. So, add this interval to the `free_time_slots` array.
47+
- Now, update the `latest_end_time` as `max(latest_end_time, end time of selected interval)`.
48+
- If the current employee has any other interval, push it into the heap.
49+
- After all the iterations, when the heap becomes empty, return the `free_time_slots` array.
50+
51+
![Solution 1](./images/solutions/employee_free_time_heap_solution_1.png)
52+
![Solution 2](./images/solutions/employee_free_time_heap_solution_2.png)
53+
![Solution 3](./images/solutions/employee_free_time_heap_solution_3.png)
54+
![Solution 4](./images/solutions/employee_free_time_heap_solution_4.png)
55+
![Solution 5](./images/solutions/employee_free_time_heap_solution_5.png)
56+
![Solution 6](./images/solutions/employee_free_time_heap_solution_6.png)
57+
![Solution 7](./images/solutions/employee_free_time_heap_solution_7.png)
58+
59+
### Time Complexity
60+
61+
The time complexity of this algorithm is O(mlog(n)), where n is the number of employees and m is the total number of
62+
intervals across all employees. This is because the time complexity of filling the heap is O(nlog(n)) and the time
63+
complexity of processing the heap is O(mlog(n)).
64+
65+
### Space Complexity
66+
67+
We use a heap in the solution, which can have a maximum of n elements. Hence, the space complexity of this solution is
68+
O(n), where n is the number of employees.
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
from typing import List, Tuple
2+
import heapq
3+
from algorithms.intervals.employee_free_time.interval import Interval
4+
5+
6+
def employee_free_time(schedules: List[List[Interval]]) -> List[Interval]:
7+
"""
8+
Finds intervals where employees have free time
9+
10+
Complexity:
11+
Time Complexity: O(NlogN), where N is the total number of intervals across all employees. This is dominated by the sorting step.
12+
Space Complexity: O(N) to store the flattened all_schedules list.
13+
14+
Args:
15+
schedules(list): a list of lists, where each inner list contains `Interval` objects representing an employee's schedule.
16+
Returns:
17+
list: Intervals of employee free time
18+
"""
19+
# We need to combine and merge all employee intervals(schedules) into one unified schedule to be able to check
20+
# for free time. Since using a new list incurs a space cost of O(n), we can modify the input list, however, this
21+
# has side effects if the schedule list is used in other parts of the program. To avoid side effects, we copy over
22+
# the input list into a new list to handle the merging
23+
all_schedules: List[Interval] = []
24+
for schedule in schedules:
25+
for schedule_interval in schedule:
26+
all_schedules.append(schedule_interval)
27+
28+
# sort by start time
29+
all_schedules.sort(key=lambda x: x.start)
30+
31+
# Keep track of the latest meeting's end time. This is initialized the first schedule's end time
32+
latest_end = all_schedules[0].end
33+
34+
# This will keep track of the free time slots
35+
free_time_slots: List[Interval] = []
36+
37+
# Now we need to find the gaps in the combined schedule. Since we have already used the first schedule's end time
38+
# as the latest end we have seen so far, we start at the next interval.
39+
for idx in range(1, len(all_schedules)):
40+
current_schedule = all_schedules[idx]
41+
current_interval_start, current_interval_end = (
42+
current_schedule.start,
43+
current_schedule.end,
44+
)
45+
46+
# If the current interval's start time is greater than the latest end time we have seen so far, it means we have
47+
# found free time
48+
if current_interval_start > latest_end:
49+
# we have a free time slot
50+
free_interval = Interval(start=latest_end, end=current_interval_start)
51+
free_time_slots.append(free_interval)
52+
53+
# update the latest_end to the maximum of the latest end time seen so far
54+
latest_end = max(latest_end, current_interval_end)
55+
56+
return free_time_slots
57+
58+
59+
def employee_free_time_heap(schedules: List[List[Interval]]) -> List[Interval]:
60+
"""
61+
Finds intervals where employees have free time using a min heap
62+
63+
Args:
64+
schedules(list): a list of lists, where each inner list contains `Interval` objects representing an employee's schedule.
65+
Returns:
66+
list: Intervals of employee free time
67+
"""
68+
heap: List[Tuple[int, int, int]] = []
69+
# Iterate for all employees' schedules
70+
# and add start of each schedule's first interval along with
71+
# its index value and a value 0.
72+
for i in range(len(schedules)):
73+
heap.append((schedules[i][0].start, i, 0))
74+
75+
# Create heap from array elements.
76+
heapq.heapify(heap)
77+
78+
# Take an empty array to store results.
79+
free_time_slots = []
80+
81+
# Set 'latest_end_time' to the start time of first interval in heap.
82+
latest_end_time = schedules[heap[0][1]][heap[0][2]].end
83+
84+
# Iterate till heap is empty
85+
while heap:
86+
# Pop an element from heap and set value of employee_index and interval_index
87+
_, employee_index, interval_index = heapq.heappop(heap)
88+
89+
# Select an interval
90+
schedule_interval = schedules[employee_index][interval_index]
91+
92+
# If selected interval's start value is greater than the
93+
# latest_end_time value, it means that this interval is free.
94+
# So, add this interval (latest_end_time, interval's end value) into result.
95+
if schedule_interval.start > latest_end_time:
96+
free_time_slots.append(Interval(latest_end_time, schedule_interval.start))
97+
98+
# Update the latest_end_time as maximum of latest_end_time and interval's end value.
99+
latest_end_time = max(latest_end_time, schedule_interval.end)
100+
101+
# If there is another interval in current employees' schedule,
102+
# push that into heap.
103+
if interval_index + 1 < len(schedules[employee_index]):
104+
heapq.heappush(
105+
heap,
106+
(
107+
schedules[employee_index][interval_index + 1].start,
108+
employee_index,
109+
interval_index + 1,
110+
),
111+
)
112+
113+
# When the heap is empty, return result.
114+
return free_time_slots
98.2 KB
Loading
81.7 KB
Loading
73.1 KB
Loading
109 KB
Loading
105 KB
Loading
140 KB
Loading
142 KB
Loading
126 KB
Loading

0 commit comments

Comments
 (0)