Loading problems...
TL;DR: Flatten all employee schedules into a single list of intervals, sort them by start time, and merge overlapping intervals; the gaps remaining between the merged intervals represent the common free time.
The Employee Free Time problem asks us to identify time slots when no employee is working. We are provided with a schedule for each employee, consisting of non-overlapping working intervals. Our goal is to find the intersection of free time across all employees. This is a classic interval manipulation problem that requires determining the union of all working times and identifying the gaps within that union.
This problem is commonly referred to as LeetCode 759, a popular interview question at companies like DoorDash and Amazon because it tests the ability to manage time-based constraints and aggregate data efficiently.
A naive approach might attempt to simulate the timeline directly. One might consider creating a boolean array or hash map representing every minute (or smallest time unit) of the day, marking indices as true if any employee is working. After populating the timeline, one would scan for contiguous false blocks.
Pseudo-code:
text
MinStart = earliest start time
MaxEnd = latest end time
Timeline = array of size (MaxEnd - MinStart) initialized to false
For each employee in schedule:
For each interval [start, end]:
Mark Timeline[start to end] as true
Iterate through Timeline:
Collect sequences of false values as free intervalsWhy this fails:
The core insight for solving LeetCode 759 is that the distinction between different employees does not matter. Whether Employee A works from 1:00 to 3:00 and Employee B works from 2:00 to 4:00, or if a single employee works both shifts, the "busy" time is the continuous block from 1:00 to 4:00.
Therefore, we can flatten the input. Instead of a list of lists, we treat the input as a single large collection of working intervals.
Once flattened, the problem transforms into finding gaps in a set of intervals. To do this efficiently, we must process time chronologically. By sorting all intervals by their start time, we enforce an invariant: as we iterate, we never encounter an interval that starts earlier than the one we just processed.
With a sorted list, we can maintain a running max_end variable representing the end of the current continuous block of work. When we encounter a new interval:
max_end, a gap exists. The free time is [max_end, current_interval.start].max_end, it overlaps (or touches) the current block. We merge them by updating max_end to be the maximum of the current max_end and the new interval's end.Visual Description: Imagine drawing all intervals on a horizontal timeline. Sorting aligns them left-to-right based on when they begin. We place a pointer at the end of the first interval. We look at the next interval in the sorted list. If there is empty space between our pointer and the start of this next interval, that space is "Free Time." If the next interval overlaps with our pointer, we extend our pointer to the end of that interval (if it extends further). We repeat this sweep until the end.

Consider the input: schedule = [[[1,2],[5,6]], [[1,3]], [[4,10]]]
[[1,2], [5,6], [1,3], [4,10]][[1,2], [1,3], [4,10], [5,6]] (Sorted by start time)max_end = 2 (from [1,2])result = [][1,3]
1 > 2? No.max_end = max(2, 3) = 3.[4,10]
4 > 3? Yes.[3, 4]. Add to result.max_end = 10.[5,6]
5 > 10? No.max_end = max(10, 6) = 10.[[3, 4]].The algorithm relies on the Greedy Choice Property. By sorting intervals by start time, we ensure that we process potential overlaps in chronological order. When we track max_end, we are effectively maintaining the "frontier" of the current continuous working block.
If the next earliest starting interval begins after max_end, it is impossible for any other interval to cover that gap, because all subsequent intervals also have start times greater than or equal to the current one (due to sorting). Thus, the gap [max_end, current.start] is definitively free time. This guarantees that the algorithm finds all global free time intervals correctly.
cpp/* // Definition for an Interval. class Interval { public: int start; int end; Interval() {} Interval(int _start, int _end) { start = _start; end = _end; } }; */ class Solution { public: vector<Interval> employeeFreeTime(vector<vector<Interval>> schedule) { vector<Interval> allIntervals; // Flatten the schedule into a single list for (const auto& employee : schedule) { for (const auto& interval : employee) { allIntervals.push_back(interval); } } // Sort by start time sort(allIntervals.begin(), allIntervals.end(), [](const Interval& a, const Interval& b) { return a.start < b.start; }); vector<Interval> result; if (allIntervals.empty()) return result; // Initialize max_end with the first interval's end int max_end = allIntervals[0].end; // Iterate through sorted intervals for (size_t i = 1; i < allIntervals.size(); ++i) { if (allIntervals[i].start > max_end) { // Gap detected result.push_back(Interval(max_end, allIntervals[i].start)); max_end = allIntervals[i].end; } else { // Overlap or touching, extend the current block max_end = max(max_end, allIntervals[i].end); } } return result; } };
java/* // Definition for an Interval. class Interval { public int start; public int end; public Interval() {} public Interval(int _start, int _end) { start = _start; end = _end; } }; */ class Solution { public List<Interval> employeeFreeTime(List<List<Interval>> schedule) { List<Interval> allIntervals = new ArrayList<>(); // Flatten the schedule for (List<Interval> employee : schedule) { allIntervals.addAll(employee); } // Sort by start time Collections.sort(allIntervals, (a, b) -> a.start - b.start); List<Interval> result = new ArrayList<>(); if (allIntervals.isEmpty()) return result; // Initialize tracking variable int maxEnd = allIntervals.get(0).end; // Iterate through sorted intervals for (int i = 1; i < allIntervals.size(); i++) { Interval current = allIntervals.get(i); if (current.start > maxEnd) { // Found a free time gap result.add(new Interval(maxEnd, current.start)); maxEnd = current.end; } else { // Merge intervals maxEnd = Math.max(maxEnd, current.end); } } return result; } }
python""" # Definition for an Interval. class Interval: def __init__(self, start: int = None, end: int = None): self.start = start self.end = end """ class Solution: def employeeFreeTime(self, schedule: '[[Interval]]') -> '[Interval]': # Flatten the list of lists into a single list all_intervals = [] for emp in schedule: for interval in emp: all_intervals.append(interval) # Sort intervals by start time all_intervals.sort(key=lambda x: x.start) result = [] if not all_intervals: return result # Initialize the end of the first working block max_end = all_intervals[0].end # Iterate through the rest of the intervals for i in range(1, len(all_intervals)): current = all_intervals[i] if current.start > max_end: # Gap found between max_end and current.start result.append(Interval(max_end, current.start)) max_end = current.end else: # Overlap: extend the current working block max_end = max(max_end, current.end) return result
Let be the total number of intervals across all employees.
The Greedy - Interval Merging pattern is highly reusable. Understanding how to sort and sweep through intervals solves a specific class of problems efficiently.
max_end correctly. When intervals overlap, max_end must be the maximum of the current max_end and the new interval's end, not just the new interval's end. A smaller interval can be completely contained within a larger previous one.