Given an array of intervals where intervals[i] = [start_i, end_i], merge all overlapping intervals, and return an array of the non-overlapping intervals that cover all the intervals in the input.
You may return the answer in any order.
Note: Intervals are non-overlapping if they have no common point. For example, [1, 2] and [3, 4] are non-overlapping, but [1, 2] and [2, 3] are overlapping.
Example 1:
Input: intervals = [[1,3],[1,5],[6,7]]
Output: [[1,5],[6,7]]Example 2:
Input: intervals = [[1,2],[2,3]]
Output: [[1,3]]Constraints:
1 <= intervals.length <= 1000intervals[i].length == 20 <= start <= end <= 1000
You should aim for a solution as good or better than O(nlogn) time and O(n) space, where n is the size of the input array.
Sorting the given intervals in ascending order based on their start values is beneficial, as it helps in identifying overlapping intervals efficiently. How can you determine if two intervals overlap?
If two intervals are sorted in ascending order by their start values, they overlap if the start value of the second interval is less than or equal to the end value of the first interval.
We iterate through the sorted intervals from left to right, starting with the first interval in the output list. From the second interval onward, we compare each interval with the last appended interval. Can you determine the possible cases for this comparison?
The two cases are: if the current interval overlaps with the last appended interval, we update its end value to the maximum of both intervals' end values and continue. Otherwise, we append the current interval and proceed.
Before attempting this problem, you should be comfortable with:
We are given a list of intervals, and some of them may overlap.
The goal is to merge all overlapping intervals so that the final list contains only non-overlapping intervals, covering the same ranges.
A natural way to approach this is:
So after sorting:
output with the first interval.(start, end) in the sorted list:lastEnd be the end of the last interval in output.start <= lastEnd):output[-1][1] = max(lastEnd, end)output as a new intervaloutputclass Solution:
def merge(self, intervals: List[List[int]]) -> List[List[int]]:
intervals.sort(key=lambda pair: pair[0])
output = [intervals[0]]
for start, end in intervals:
lastEnd = output[-1][1]
if start <= lastEnd:
output[-1][1] = max(lastEnd, end)
else:
output.append([start, end])
return outputThis approach treats each interval as a pair of events on a number line:
startendInstead of merging intervals directly, we track how many intervals are currently active as we move from left to right along the number line.
The key idea:
0 → positive, a merged interval startspositive → 0, a merged interval endsBy recording how the count changes at each boundary and sweeping through them in order, we can reconstruct all merged intervals.
mp to store boundary changes:[start, end]:mp[start] (interval starts)mp[end] (interval ends)have = 0 to track how many intervals are currently activeinterval = [] to build the current merged intervalres = [] to store the final merged intervalsmp in sorted order:i:interval is empty, this position may mark the start of a new merged interval:interval = [i]have += mp[i]have == 0:i as the end of the current merged intervalinterval to resinterval to emptyresclass Solution:
def merge(self, intervals: List[List[int]]) -> List[List[int]]:
mp = defaultdict(int)
for start, end in intervals:
mp[start] += 1
mp[end] -= 1
res = []
interval = []
have = 0
for i in sorted(mp):
if not interval:
interval.append(i)
have += mp[i]
if have == 0:
interval.append(i)
res.append(interval)
interval = []
return resWe want to merge all overlapping intervals so the result contains only non-overlapping ranges.
This solution uses a greedy idea with an auxiliary array:
start, we record the farthest end of any interval that begins at starthave)While scanning:
i, we may need to extend our current merged interval’s end to include itSo the scan behaves like:
max_val).mp of size max_val + 1:mp[start] will store the farthest (end + 1) among intervals that start at start[start, end]:mp[start] = max(mp[start], end + 1)res as an empty list for merged intervalsinterval_start = -1 to mark the start of the current merged intervalhave = -1 to track the farthest end of the current merged intervali from 0 to len(mp) - 1:mp[i] != 0, it means some interval starts at i:interval_start = ihave = max(have, mp[i] - 1) (convert back to real end)have == i, it means we reached the end of the current merged interval:[interval_start, have] to resinterval_start and haveres.res.class Solution:
def merge(self, intervals: List[List[int]]) -> List[List[int]]:
max_val = max(interval[0] for interval in intervals)
mp = [0] * (max_val + 1)
for start, end in intervals:
mp[start] = max(end + 1, mp[start])
res = []
have = -1
interval_start = -1
for i in range(len(mp)):
if mp[i] != 0:
if interval_start == -1:
interval_start = i
have = max(mp[i] - 1, have)
if have == i:
res.append([interval_start, have])
have = -1
interval_start = -1
if interval_start != -1:
res.append([interval_start, have])
return resWhere is the length of the array and is the maximum start value among all the intervals.
Two intervals overlap when current.start <= last.end, not <. Intervals [1, 3] and [3, 5] are considered overlapping and should merge to [1, 5].
When merging overlapping intervals, the new end should be max(last.end, current.end), not just current.end. An earlier interval might extend further than a later-starting one.
The sorting approach assumes intervals are processed in order of start time. Without sorting, you cannot guarantee that overlapping intervals are adjacent, leading to missed merges.