Difficulty: Medium, Asked-in: Amazon, Adobe, Hike.
Key takeaway: An excellent problem to learn problem solving using sorting.
Given an array of intervals where the start and end points of each interval are provided, write a program to merge all overlapping intervals and return an array of non-overlapping intervals that cover all the intervals.
Input: intervals = [[9, 12], [3, 7], [1, 4], [14, 17]], Output: [[1, 7], [9, 12], [14, 17]].
Explanation: Since intervals [1, 4] and [3, 7] are overlapping, we merge them into [1, 7].
Input: intervals = [[1, 6], [6, 9]], Output: [[1, 9]].
Explanation: Intervals [1, 6] and [6, 9] are overlapping.
Input: intervals = [[8, 10], [2, 4], [1, 3], [5, 9]], Output: [[1, 4], [5, 10]].
Explanation: Intervals [2, 4] and [1, 3] are overlapping, we merge them into [1, 4]. Similarly, we merge [8, 10] and [5, 9] into [5, 10].
The idea is to sort the intervals based on their start times and then merge them iteratively using a stack. Here, sorting will help us identify the overlapping intervals, and the stack will keep track of the merged intervals.
Now, we create a stack to store intervals and run a loop to access each interval.
mergeIntervals(intervals)
sort intervals by start value
stack = empty stack
for each curr in intervals
if stack is empty or stack.top().end < curr.start
push curr onto stack
else if stack.top().end < curr.end
stack.top().end = curr.end
output = empty list
while stack is not empty
add stack.top() to output
pop stack
return outputWe are sorting the interval and running a single loop to merge intervals using stack. Suppose we are using some efficient O(nlogn) sorting algorithm like quick sort, merge sort or heap sort.
At each iteration of the single loop, we are doing O(1) operation to merge intervals. So overall time complexity = Time complexity of sorting + O(n)*O(1) = O(nlogn) + O(n) = O(nlogn).
Space complexity depends on the implementation of the sorting algorithm and the size of the stack. In the worst case, the size of the stack will be O(n) when all intervals are non-overlapping. If we use merge sort, then space complexity = O(n) + O(n) = O(n). If we use heap sort, then space complexity = O(1) + O(n) = O(n).
The above solution requires O(n) additional space for the stack. How can we solve this problem without using a stack? Here is an observation: If we sort the intervals based on their start values, we can easily identify and merge overlapping intervals in a single scan. The idea is simple: Sorting ensures that overlapping intervals are adjacent to each other.
mergeIntervals(intervals, n)
output = empty list
if n == 0
return output
sort intervals by start value
currentInterval = intervals[0]
for i from 1 to n - 1
if currentInterval.end >= intervals[i].start
currentInterval.end = max(currentInterval.end, intervals[i].end)
else
add currentInterval to output
currentInterval = intervals[i]
add currentInterval to output
return outputWe are sorting the intervals and running a single loop. Suppose we are using an efficient O(nlogn) sorting algorithm like quicksort, merge sort, or heapsort. At each iteration of the single loop, we perform an O(1) operation to merge intervals. So overall time complexity = Time complexity of sorting + O(n)*O(1) = O(nlogn) + O(n) = O(nlogn).
The space complexity depends on the implementation of the sorting algorithm. If we use merge sort, then the space complexity is O(n). If we use heapsort, then the space complexity is O(1).
The idea here is to sort the intervals based on the start time and modify the same input array to store the merged intervals. To achieve this, we maintain an index variable to track the index of the last merged interval. We update the start and end points of the new interval based on the interval present at the index.
mergeIntervals(intervals, n)
if n == 0
return 0
sort intervals by start value
index = 0
for i from 1 to n - 1
if intervals[index].end >= intervals[i].start
intervals[index].end =
max(intervals[index].end, intervals[i].end)
intervals[index].start =
min(intervals[index].start, intervals[i].start)
else
index = index + 1
intervals[index] = intervals[i]
return index + 1We are sorting the interval and running a single loop to merge intervals. So overall time complexity = Time complexity of sorting + O(n)*O(1) = O(nlogn) + O(n) = O(nlogn).
Since we store the merged intervals in the same input array, the space complexity depends only on the sorting algorithm. If merge sort, the space complexity is O(n). If heap sort, the space complexity is O(1).
If you have any queries or feedback, please write us at contact@enjoyalgorithms.com. Enjoy learning, Enjoy algorithms!