Given an array of intervals where intervals[i] = [starti, endi], merge all overlapping intervals, and return an array of the non-overlapping intervals that cover all the intervals in the input.
Example 1:
Input: intervals = [[1,3],[2,6],[8,10],[15,18]]
Output: [[1,6],[8,10],[15,18]]
Explanation: Since intervals [1,3] and [2,6] overlaps, merge them into [1,6].
Example 2:
Input: intervals = [[1,4],[4,5]]
Output: [[1,5]]
Explanation: Intervals [1,4] and [4,5] are considered overlapping.
An array of arrays is given. Each array nested with the outer array contains two number values. The second value will always be greater than the first value. This pair of values represents an interval. Throughout this article, the term ‘array’ will refer to the outer array and ‘interval’ will refer to an inner array. This given array is unsorted.
An array with the same structure as the given array. The difference is that this array should have no nested arrays with intervals that overlap. In other words, our job is to combine the overlapping intervals in the given array before returning it.
The problem can be broken down into three parts:
// no overlap
[1, 4], [5, 6]
[3, 6], [1, 2]
// overlap
[1, 3], [3, 5]
[2, 3], [1, 6]
[5, 7], [4, 6]
array[i][1] >= array[i + 1][0] && array[i][0] <= array[i + 1][1]
// i and j represent the indices of two different intervals of the array
[Math.min(array[i][0], array[j][0]), Math.max(array[i][1], array[j][1])]
// i and j represent the indices of two different intervals of the array
This approach will run in O(N^2) time. It will iterate through the array. At each iteration, it will iterate through the array again and compare the current interval to every other interval in the array. If it makes it through the entire array finding no overlapping interval, you will know that this specific interval cannot be combined with anything else and should be left alone. If an overlapping interval is found, the two overlapping intervals are both removed from the array and the new combined array is added in at your current index. The iteration being done at your current index is then started back from the beginning since you have a new value at your current index. This approach will get the correct answer. However, the problem can be solved in better than O(N^2) time complexity.
function mergeIntervals(array) {
for (let i = 0; i < array.length; i++) {
for (let j = 0; j < array.length; j++) {
if (i === j) continue
if (array[i][1] >= array[j][0] && array[i][0] <= array[j][1]) {
let k = [Math.min(array[i][0], array[j][0]), Math.max(array[i][1], array[j][1])]
array.splice(i, 1, k)
array.splice(j, 1)
if (i > 0) i--
}
}
}
return array
}
As stated in the Problem Solving Strategy section, this approach has the same core algorithm as the brute force method. The difference is that this time, the array is sorted before starting the loop and there is no nested loop. One way to help identify when a sorting solution is an option is simply to be aware and recognize when sorting is beneficial. Know that sorting takes O(NlogN) time. So, if your solution is already at O(NlogN) time or worse, there is not need to worry about its effect on time complexity. But does it provide value to this specific problem? Yes. In this case, sorting allows you identify and combine all overlapping intervals in a single pass. The benefit of sorting here is that you no longer have to check every single combination. Once the array is sorted, you can simply compare the intervals that are next to each other. Since the array is sorted, you now know that any overlaps that occur will be happening with two intervals that sit within one index of each other. So, you can make one pass through the array, comparing the interval at your current index to the interval at the following index. Here is a visual showcasing how sorting simplifies the problem:
// example input array
[[2, 3], [5, 7], [1, 4], [8, 9], [3, 4]]
// visual representation before sorting
1 2 3 4 5 6 7 8 9
| XXXXX | | | | | | [2, 3]
| | | | XXXXXXXXX | | [5, 7]
XXXXXXXXXXXXX | | | | | [1, 4]
| | | | | | | XXXXX [8, 9]
| | XXXXX | | | | | [3, 4]
// visual representation after sorting
1 2 3 4 5 6 7 8 9
XXXXXXXXXXXXX | | | | | [1, 4]
| XXXXX | | | | | | [2, 3]
| | XXXXX | | | | | [3, 4]
| | | | XXXXXXXXX | | [5, 7]
| | | | | | | XXXXX [8, 9]
Note that after sorting the array, all overlapping intervals that exist will now always sit next to each other.
function mergeIntervals2(array) {
array.sort((a, b) => {
if (a[0] === b[0]) {
return a[1] - b[1]
}
return a[0] - b[0]
})
for (let i = 0; i < array.length; i++) {
if (i === array.length - 1) continue
if (array[i][1] >= array[i + 1][0] && array[i][0] <= array[i + 1][1]) {
array.splice(i, 2, [Math.min(array[i][0], array[i + 1][0]), Math.max(array[i][1], array[i + 1][1])])
i--
}
}
return array
}