Skip to content

Commit 9c7f67d

Browse files
authored
Merge pull request #133 from BrianLusina/feat/algorithms-intervals-employee-free-time
feat(algorithms, prefix-sum, intervals) employee free time
2 parents 083ee03 + 735682b commit 9c7f67d

24 files changed

Lines changed: 601 additions & 5 deletions

DIRECTORY.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,9 @@
110110
* [Test Car Pooling](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/intervals/car_pooling/test_car_pooling.py)
111111
* Count Days
112112
* [Test Count Days Without Meetings](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/intervals/count_days/test_count_days_without_meetings.py)
113+
* Employee Free Time
114+
* [Interval](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/intervals/employee_free_time/interval.py)
115+
* [Test Employee Free Time](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/intervals/employee_free_time/test_employee_free_time.py)
113116
* Full Bloom Flowers
114117
* [Test Full Bloom Flowers](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/intervals/full_bloom_flowers/test_full_bloom_flowers.py)
115118
* Insert Interval
@@ -129,6 +132,9 @@
129132
* Memoization
130133
* [Fibonacci](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/memoization/fibonacci.py)
131134
* [Petethebaker](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/petethebaker.py)
135+
* Prefix Sum
136+
* Continous Sub Array Sum
137+
* [Test Check Subarray Sum](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/prefix_sum/continous_sub_array_sum/test_check_subarray_sum.py)
132138
* Search
133139
* Binary Search
134140
* Divide Chocolate
@@ -250,6 +256,8 @@
250256
* [Min Increment For Unique](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/arrays/minincrementsforunique/min_increment_for_unique.py)
251257
* Non Overlapping Intervals
252258
* [Test Non Overlapping Intervals](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/arrays/non_overlapping_intervals/test_non_overlapping_intervals.py)
259+
* Sub Array With Sum
260+
* [Test Sub Array With Sum](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/arrays/sub_array_with_sum/test_sub_array_with_sum.py)
253261
* Subarrays With Fixed Bounds
254262
* [Test Subarrays With Fixed Bounds](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/arrays/subarrays_with_fixed_bounds/test_subarrays_with_fixed_bounds.py)
255263
* Circular Buffer
@@ -937,7 +945,6 @@
937945
* [Test Lonely Integer](https://github.com/BrianLusina/PythonSnips/blob/master/tests/datastructures/arrays/test_lonely_integer.py)
938946
* [Test Longest Consecutive Sequence](https://github.com/BrianLusina/PythonSnips/blob/master/tests/datastructures/arrays/test_longest_consecutive_sequence.py)
939947
* [Test Max Subarray](https://github.com/BrianLusina/PythonSnips/blob/master/tests/datastructures/arrays/test_max_subarray.py)
940-
* [Test Sub Array With Sum](https://github.com/BrianLusina/PythonSnips/blob/master/tests/datastructures/arrays/test_sub_array_with_sum.py)
941948
* [Test Zig Zag Sequence](https://github.com/BrianLusina/PythonSnips/blob/master/tests/datastructures/arrays/test_zig_zag_sequence.py)
942949
* Linked List
943950
* [Test Reorder List](https://github.com/BrianLusina/PythonSnips/blob/master/tests/datastructures/linked_list/test_reorder_list.py)
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: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
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+
if not all_schedules:
29+
return []
30+
31+
# sort by start time
32+
all_schedules.sort(key=lambda x: x.start)
33+
34+
# Keep track of the latest meeting's end time. This is initialized the first schedule's end time
35+
latest_end = all_schedules[0].end
36+
37+
# This will keep track of the free time slots
38+
free_time_slots: List[Interval] = []
39+
40+
# Now we need to find the gaps in the combined schedule. Since we have already used the first schedule's end time
41+
# as the latest end we have seen so far, we start at the next interval.
42+
for idx in range(1, len(all_schedules)):
43+
current_schedule = all_schedules[idx]
44+
current_interval_start, current_interval_end = (
45+
current_schedule.start,
46+
current_schedule.end,
47+
)
48+
49+
# If the current interval's start time is greater than the latest end time we have seen so far, it means we have
50+
# found free time
51+
if current_interval_start > latest_end:
52+
# we have a free time slot
53+
free_interval = Interval(start=latest_end, end=current_interval_start)
54+
free_time_slots.append(free_interval)
55+
56+
# update the latest_end to the maximum of the latest end time seen so far
57+
latest_end = max(latest_end, current_interval_end)
58+
59+
return free_time_slots
60+
61+
62+
def employee_free_time_heap(schedules: List[List[Interval]]) -> List[Interval]:
63+
"""
64+
Finds intervals where employees have free time using a min heap
65+
66+
Args:
67+
schedules(list): a list of lists, where each inner list contains `Interval` objects representing an employee's schedule.
68+
Returns:
69+
list: Intervals of employee free time
70+
"""
71+
heap: List[Tuple[int, int, int]] = []
72+
# Iterate for all employees' schedules
73+
# and add start of each schedule's first interval along with
74+
# its index value and a value 0.
75+
for i in range(len(schedules)):
76+
if schedules[i]: # Only add if employee has at least one interval
77+
heap.append((schedules[i][0].start, i, 0))
78+
79+
# Create heap from array elements.
80+
heapq.heapify(heap)
81+
82+
# Handle empty heap
83+
if not heap:
84+
return []
85+
86+
# Take an empty array to store results.
87+
free_time_slots = []
88+
89+
# Set 'latest_end_time' to the start time of first interval in heap.
90+
latest_end_time = schedules[heap[0][1]][heap[0][2]].end
91+
92+
# Iterate till heap is empty
93+
while heap:
94+
# Pop an element from heap and set value of employee_index and interval_index
95+
_, employee_index, interval_index = heapq.heappop(heap)
96+
97+
# Select an interval
98+
schedule_interval = schedules[employee_index][interval_index]
99+
100+
# If selected interval's start value is greater than the
101+
# latest_end_time value, it means that this interval is free.
102+
# So, add this interval (latest_end_time, interval's end value) into result.
103+
if schedule_interval.start > latest_end_time:
104+
free_time_slots.append(Interval(latest_end_time, schedule_interval.start))
105+
106+
# Update the latest_end_time as maximum of latest_end_time and interval's end value.
107+
latest_end_time = max(latest_end_time, schedule_interval.end)
108+
109+
# If there is another interval in current employees' schedule,
110+
# push that into heap.
111+
if interval_index + 1 < len(schedules[employee_index]):
112+
heapq.heappush(
113+
heap,
114+
(
115+
schedules[employee_index][interval_index + 1].start,
116+
employee_index,
117+
interval_index + 1,
118+
),
119+
)
120+
121+
# When the heap is empty, return result.
122+
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

0 commit comments

Comments
 (0)