The Employee Free Time problem asks you to find the common free intervals across multiple employees, given each employee's working schedule. Each employee's schedule is a list of non-overlapping intervals representing the times they are working. The goal is to return a list of finite intervals representing the times when all employees are free (i.e., not working).
[start, end]
where start < end
, and intervals do not overlap or touch.
schedule
is a list of lists of intervals. Each inner list is one employee's schedule.
At first, you might consider brute-forcing by checking every possible time and seeing if all employees are free at that moment. However, this is inefficient and unnecessary. Instead, realize that the problem is about merging intervals: if we know when employees are busy, we can merge all their busy times together, and then the gaps between those merged intervals are the free times.
Conceptually, this is similar to merging calendar events from multiple people and finding the gaps when no one is in a meeting. The challenge is efficiently merging all intervals and then finding the gaps between them.
The optimal approach is to:
We use sorting because it allows us to process intervals in order and easily merge overlaps. Merging is efficient because we only need to look at the previous interval to see if there is overlap.
Suppose we have the following 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]]
→ after sorting by start: [[1,2],[1,3],[4,10],[5,6]]
[1,2]
[1,3]
overlaps with [1,2]
→ merge to [1,3]
[4,10]
does not overlap with [1,3]
→ add [4,10]
[5,6]
overlaps with [4,10]
→ merge to [4,10]
[[1,3],[4,10]]
[1,3]
and [4,10]
is [3,4]
[[3,4]]
# Definition for an Interval.
# class Interval:
# def __init__(self, start: int, end: int):
# self.start = start
# self.end = end
def employeeFreeTime(schedule):
# Flatten all intervals
intervals = [iv for emp in schedule for iv in emp]
# Sort intervals by start time
intervals.sort(key=lambda x: x.start)
# Merge intervals
merged = []
for iv in intervals:
if not merged or merged[-1].end < iv.start:
merged.append(iv)
else:
merged[-1].end = max(merged[-1].end, iv.end)
# Find gaps
res = []
for i in range(1, len(merged)):
if merged[i-1].end < merged[i].start:
res.append(Interval(merged[i-1].end, merged[i].start))
return res
/*
// Definition for an Interval.
class Interval {
public:
int start;
int end;
Interval() : start(0), end(0) {}
Interval(int s, int e) : start(s), end(e) {}
};
*/
vector<Interval> employeeFreeTime(vector<vector<Interval>>& schedule) {
vector<Interval> intervals;
for (auto& emp : schedule)
for (auto& iv : emp)
intervals.push_back(iv);
sort(intervals.begin(), intervals.end(), [](Interval& a, Interval& b) {
return a.start < b.start;
});
vector<Interval> merged;
for (auto& iv : intervals) {
if (merged.empty() || merged.back().end < iv.start)
merged.push_back(iv);
else
merged.back().end = max(merged.back().end, iv.end);
}
vector<Interval> res;
for (int i = 1; i < merged.size(); ++i) {
if (merged[i-1].end < merged[i].start)
res.push_back(Interval(merged[i-1].end, merged[i].start));
}
return res;
}
/*
// Definition for an Interval.
class Interval {
public int start;
public int end;
public Interval() { start = 0; end = 0; }
public Interval(int s, int e) { start = s; end = e; }
}
*/
public List<Interval> employeeFreeTime(List<List<Interval>> schedule) {
List<Interval> intervals = new ArrayList<>();
for (List<Interval> emp : schedule)
intervals.addAll(emp);
intervals.sort((a, b) -> a.start - b.start);
List<Interval> merged = new ArrayList<>();
for (Interval iv : intervals) {
if (merged.isEmpty() || merged.get(merged.size()-1).end < iv.start)
merged.add(iv);
else
merged.get(merged.size()-1).end = Math.max(merged.get(merged.size()-1).end, iv.end);
}
List<Interval> res = new ArrayList<>();
for (int i = 1; i < merged.size(); ++i) {
if (merged.get(i-1).end < merged.get(i).start)
res.add(new Interval(merged.get(i-1).end, merged.get(i).start));
}
return res;
}
/**
* // Definition for an Interval.
* function Interval(start, end) {
* this.start = start;
* this.end = end;
* }
*/
/**
* @param {Interval[][]} schedule
* @return {Interval[]}
*/
var employeeFreeTime = function(schedule) {
let intervals = [];
for (let emp of schedule)
for (let iv of emp)
intervals.push(iv);
intervals.sort((a, b) => a.start - b.start);
let merged = [];
for (let iv of intervals) {
if (merged.length === 0 || merged[merged.length-1].end < iv.start)
merged.push(iv);
else
merged[merged.length-1].end = Math.max(merged[merged.length-1].end, iv.end);
}
let res = [];
for (let i = 1; i < merged.length; ++i) {
if (merged[i-1].end < merged[i].start)
res.push(new Interval(merged[i-1].end, merged[i].start));
}
return res;
};
So, the overall time complexity is O(N log N) due to sorting.
The space complexity is O(N) for storing all intervals and the merged list.
The Employee Free Time problem is elegantly solved by merging all employees' working intervals and then finding the gaps between those merged intervals. This approach leverages sorting and interval merging, which are efficient and easy to implement. The key insight is to shift from checking all possible times to simply working with interval endpoints, making the solution both concise and optimal.