|
| 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 |
0 commit comments