Trigger: you must use every interval. Sort by start, keep the last, extend it when the next one overlaps, otherwise start a new one, then read the answer off what you built. Not sure this is your page? Which question is it asking?
Most groups here run one loop, and differ in what they read off the result, where the intervals come from, or what lies between them. The exceptions say so in their intros, and the last group, two sorted lists, has no merge at all.
Combining? Look at the entrance. Meet each interval as it starts, stretch the last one or start a new one, then read the answer off what you built: a list, a flag, a number, the gaps, or the blocks.
The loop: merge and insert
Merge overlapping intervals
Sort by start, then walk the list against the last interval kept: if the two intersect, replace that last one with the pair merged; otherwise it is finished, so append the current interval as the new last. The two primitives from the index are the entire body of the loop.
def merge(intervals):
intervals.sort()
doesIntersect = lambda a, b: (a[1] >= b[0] and b[1] >= a[0])
getMerged = lambda a, b: (min(a[0], b[0]), max(a[1], b[1]))
merged = []
for inter in intervals:
if merged and doesIntersect(merged[-1], inter):
merged[-1] = getMerged(merged[-1], inter)
else:
merged.append(inter)
return merged
Starting from an empty merged and guarding with if merged and ... avoids seeding the list with intervals[0] as a special case, and intervals.sort() sorts by start already - tuples and lists compare element by element, so the start is the primary key for free.
Sorting by start is what makes this a single pass: once an interval is confirmed done (the next start is past its end), nothing later in the sorted list can ever reach back and overlap it again.
Open all gotchas
Without sorting, merging is not just slower - it's a different, harder algorithm Why?
A merge can create an overlap that didn't exist before - [1,3] merged with [2,8] becomes [1,8], which may now reach an interval neither one touched - so a brute-force pass has to restart after every merge: O(n3 ) worst case, not O(n2 ). Sorting doesn't speed up the same algorithm, it replaces "recheck everything after every change" with "adjacent is already in the right order."
Sort by start, not end - sorting by end can hide a merge Why?
Take [2,3], [4,5], [1,10]. By end the order is unchanged, and the loop emits [2,3], then merges [4,5] into [1,10] - which never looks back to absorb [2,3], though it fully contains it. Result [[2,3], [1,10]], wrong. By start it's [1,10], [2,3], [4,5] and both fold in on contact. Sorting by start guarantees nothing still ahead starts earlier than what you're merging into; sorting by end guarantees nothing.
Losing the last interval Why?
The seed-and-append shape - start with merged = [intervals[0]], append inside the else - only flushes an interval when a later one fails to overlap, so the final accumulator is still in hand when the loop ends and has to be appended after it. The if merged and ... form used above sidesteps this entirely by never holding an interval outside the list.
Insert a new interval
A variant of merging: the list is already sorted and non-overlapping, and a single new interval needs to be dropped in - possibly merging with whatever it touches. Sorting the whole list again would work but throws away the fact that it was already sorted; a single linear scan in three phases does the same job without it.
def insert(intervals, new_interval):
result = []
i = 0
# Phase 1: intervals entirely before new_interval - copy through untouched
while i < len(intervals) and intervals[i][1] < new_interval[0]:
result.append(intervals[i])
i += 1
# Phase 2: intervals that overlap new_interval - absorb them into it
while i < len(intervals) and intervals[i][0] <= new_interval[1]:
new_interval[0] = min(new_interval[0], intervals[i][0])
new_interval[1] = max(new_interval[1], intervals[i][1])
i += 1
result.append(new_interval)
# Phase 3: intervals entirely after new_interval - copy through untouched
while i < len(intervals):
result.append(intervals[i])
i += 1
return result
[1,2] [3,5] [6,7] [9,10] new_interval = [4,8] absorbed into [3,8] Tap to open Phase 1 copies [1,2] through untouched; phase 2 absorbs [3,5] and [6,7] into the new interval, widening it to [3,8]; phase 3 copies [9,10] through untouched.
The two while conditions are asymmetric on purpose: phase 1 stops as soon as an interval's end reaches new_interval's start (it might still overlap), while phase 2 keeps going as long as an interval's start is within new_interval's current end (which grows as more intervals are absorbed) - that growing boundary is exactly why new_interval[1] has to be re-checked with max() on every absorption, not just the first.
The merged list
The merged ranges themselves are the answer. This is the loop worth memorising; the groups below change only what they read off it, where the intervals come from, or what lies between them.
56. Merge Intervals
Sort by Start, Merge in Place
FIG. 56 MERGE INTERVALS ● INTERACTIVE
visualization loads as you reach it
class Solution:
def merge(self, intervals: List[List[int]]) -> List[List[int]]:
intervals.sort()
getMerged = lambda a, b: (min(a[0], b[0]), max(a[1], b[1]))
doesIntersect = lambda a, b: (a[1] >= b[0] and b[1] >= a[0])
merged = []
for inter in intervals:
if merged and doesIntersect(merged[-1], inter):
merged[-1] = getMerged(merged[-1], inter)
else:
merged.append(inter)
return merged
How it worksO(n + n log n) · O(sort)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - top ↑
228. Summary Ranges
time space
Merge Consecutive Runs O(2n) time O(n) aux
Merge While Formatting O(n) time best time O(n) aux Track Only the Last Run O(n) time best time O(1) aux best space FIG. 228 SUMMARY RANGES TWO PASS ● INTERACTIVE
visualization loads as you reach it
class Solution:
def summaryRanges(self, nums: List[int]) -> List[str]:
getMerged = lambda a, b: (min(a[0], b[0]), max(a[1], b[1]))
doesIntersect = lambda a, b: (a[1] >= b[0] and b[1] >= a[0])
merged = []
for i in nums:
inter = [i - 1, i]
if merged and doesIntersect(merged[-1], inter):
merged[-1] = getMerged(merged[-1], inter)
else:
merged.append([i, i])
res = []
for i, j in merged:
if i == j:
res.append(str(i))
else:
res.append(f"{i}->{j}")
return res
How it worksO(2n) · O(n)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - top ↑
57. Insert Interval
FIG. 57 INSERT INTERVAL ● INTERACTIVE
visualization loads as you reach it
class Solution:
def insert(self, intervals: List[List[int]], newInterval: List[int]) -> List[List[int]]:
getMerged = lambda a, b: (min(a[0], b[0]), max(a[1], b[1]))
doesIntersect = lambda a, b: (a[1] >= b[0] and b[1] >= a[0])
isInGapStrictly = lambda left, right, a: a[0] > left[1] and a[1] < right[0]
merged = [[-1, -1]]
for inter in intervals:
if doesIntersect(newInterval, inter):
inter = getMerged(newInterval, inter)
elif isInGapStrictly(merged[-1], inter, newInterval):
merged.append(newInterval)
if merged and doesIntersect(merged[-1], inter):
merged[-1] = getMerged(merged[-1], inter)
else:
merged.append(inter)
last_inter = intervals[-1] if intervals else [-1, -1]
if isInGapStrictly(last_inter, [float("inf"), float("inf")], newInterval):
merged.append(newInterval)
return merged[1:]
How it worksO(n) · O(n)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - top ↑
Does anything overlap
Keep a boolean, not the list. Merge everything and compare the count, or bail out the first time the sweep would merge. With only two intervals the sweep collapses to a single overlap test. Mind the endpoint rule: Meeting Rooms treats a shared endpoint as no conflict, Determine if Two Events Have Conflict treats it as one.
252. Meeting Rooms
time space
Merge and Compare Lengths O(n + n log n) time O(sort + n) aux
Early-Exit Merge Check O(n + n log n) time O(sort + n) aux
Track Last Interval Only O(n + n log n) time O(sort) aux best space
Merge and Compare Lengths
FIG. 252 MEETING ROOMS MERGE ● INTERACTIVE
visualization loads as you reach it
class Solution:
def canAttendMeetings(self, intervals: List[List[int]]) -> bool:
intervals.sort()
getMerged = lambda a, b: (min(a[0], b[0]), max(a[1], b[1]))
doesIntersectStrictly = lambda a, b: (a[1] > b[0] and b[1] > a[0])
merged = []
for inter in intervals:
if merged and doesIntersectStrictly(merged[-1], inter):
merged[-1] = getMerged(merged[-1], inter)
else:
merged.append(inter)
return len(merged) == len(intervals)
How it worksO(n + n log n) · O(sort + n)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - top ↑
2446. Determine if Two Events Have Conflict
2446 Determine if Two Events Have Conflict LeetCode
Normalize, Then Shared Overlap Test
FIG. 2446 DETERMINE IF TWO EVENTS HAVE CONFLICT ● INTERACTIVE
visualization loads as you reach it
class Solution:
def haveConflict(self, event1: List[str], event2: List[str]) -> bool:
def toMinutes(hhmm):
hh, mm = map(int, hhmm.split(":"))
return hh * 60 + mm
doesIntersect = lambda a, b: (a[1] >= b[0] and b[1] >= a[0])
a = list(map(toMinutes, event1))
b = list(map(toMinutes, event2))
return doesIntersect(a, b)
How it worksO(1) · O(1)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - top ↑
How much is covered
Return a number or a flag, not a list. The union's total length, how many integer points it contains, how many disjoint groups it splits into, or whether it swallows a given target range. Each needs only the last merged interval and a running answer. Remove Covered Intervals is the odd one: it tests containment instead of overlap and counts the intervals that no earlier one swallows.
2580. Count Ways to Group Overlapping Ranges
2580 Count Ways to Group Overlapping Ranges LeetCode time space
Merge, Then Count the List O(n + n log n) time O(sort + n) aux
Track Last Interval Only O(n + n log n) time O(sort) aux best space
Merge, Then Count the List
FIG. 2580 COUNT WAYS TO GROUP OVERLAPPING RANGES ● INTERACTIVE
visualization loads as you reach it
class Solution:
def countWays(self, ranges: List[List[int]]) -> int:
ranges.sort()
getMerged = lambda a, b: (min(a[0], b[0]), max(a[1], b[1]))
doesIntersect = lambda a, b: (a[1] >= b[0] and b[1] >= a[0])
merged = []
for inter in ranges:
if merged and doesIntersect(merged[-1], inter):
merged[-1] = getMerged(merged[-1], inter)
else:
merged.append(inter)
x = len(merged)
return (2**x) % ((10**9) + 7)
How it worksO(n + n log n) · O(sort + n)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - top ↑
2848. Points That Intersect With Cars
2848 Points That Intersect With Cars LeetCode time space
Merge, Then Sum O(2n + n log n) time O(sort + n) aux
Merge While Tracking the Total O(n + n log n) time best time O(sort + n) aux Track Only the Last Interval O(n + n log n) time best time O(sort) aux best space FIG. 2848 POINTS THAT INTERSECT WITH CARS MERGE THEN SUM ● INTERACTIVE
visualization loads as you reach it
class Solution:
def numberOfPoints(self, nums: List[List[int]]) -> int:
nums.sort()
getMerged = lambda a, b: (min(a[0], b[0]), max(a[1], b[1]))
doesIntersect = lambda a, b: a[1] >= b[0] and b[1] >= a[0]
merged = []
for inter in nums:
if merged and doesIntersect(merged[-1], inter):
merged[-1] = getMerged(merged[-1], inter)
else:
merged.append(inter)
res = 0
for i, j in merged:
res += j - i + 1
return res
How it worksO(2n + n log n) · O(sort + n)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - top ↑
495. Teemo Attacking
time space
Merge, Then Sum O(2n) time O(n) aux
Merge While Tracking the Total O(n) time best time O(n) aux Track Only the Last Window O(n) time best time O(1) aux best space FIG. 495 TEEMO ATTACKING MERGE THEN SUM ● INTERACTIVE
visualization loads as you reach it
class Solution:
def findPoisonedDuration(self, timeSeries: List[int], duration: int) -> int:
getMerged = lambda a, b: (min(a[0], b[0]), max(a[1], b[1]))
doesIntersect = lambda a, b: (a[1] >= b[0] and b[1] >= a[0])
intervalSize = lambda a: a[1] - a[0] + 1
merged = []
ans = 0
for i in timeSeries:
inter = [i, i + duration - 1]
if merged and doesIntersect(merged[-1], inter):
merged[-1] = getMerged(merged[-1], inter)
else:
merged.append(inter)
for inter in merged:
ans += intervalSize(inter)
return ans
How it worksO(2n) · O(n)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - top ↑
1893. Check if All the Integers in a Range Are Covered
1893 Check if All the Integers in a Range Are Covered LeetCode time space
Merge, Then Check Containment O(2n + n log n) time O(sort + n) aux
Merge While Checking Containment O(n + n log n) time O(sort + n) aux
Track Only the Last Interval O(n + n log n) time O(sort) aux best space Line Sweep O(2n + n log n) time O(sort + n) aux
Line Sweep + Counting Sort O(n + C) time best time O(C) aux best space
Merge, Then Check Containment
FIG. 1893 CHECK IF ALL THE INTEGERS IN A RANGE ARE COVERED MERGE THEN CHECK ● INTERACTIVE
visualization loads as you reach it
class Solution:
def isCovered(self, ranges: List[List[int]], left: int, right: int) -> bool:
ranges.sort()
getMerged = lambda a, b: (min(a[0], b[0]), max(a[1], b[1]))
doesIntersect = lambda a, b: a[1] >= b[0] and b[1] >= a[0]
merged = []
for inter in ranges:
inter = [inter[0] - 1, inter[1]]
if merged and doesIntersect(merged[-1], inter):
merged[-1] = getMerged(merged[-1], inter)
else:
merged.append(inter)
for inter in merged:
i, j = inter[0] + 1, inter[1]
if left >= i or right <= j:
if left >= i and right <= j:
return True
return False
How it worksO(2n + n log n) · O(sort + n)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - top ↑
1288. Remove Covered Intervals
time space
Track Kept Intervals O(n + n log n) time O(sort + n) aux
Track Last Interval Only O(n + n log n) time O(sort) aux best space FIG. 1288 REMOVE COVERED INTERVALS ● INTERACTIVE
visualization loads as you reach it
class Solution:
def removeCoveredIntervals(self, intervals: List[List[int]]) -> int:
intervals.sort(key=lambda i: (i[0], -i[1]))
count = 0
doesContain = lambda a, b: (a[0] <= b[0] and b[1] <= a[1])
merged = []
for inter in intervals:
if merged and doesContain(merged[-1], inter):
...
else:
merged.append(inter)
return len(merged)
How it worksO(n + n log n) · O(sort + n)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - top ↑
The gaps
Report the complement. Merge as usual, but report or total the gaps between consecutive merged intervals rather than the intervals themselves. Most also need the two boundary gaps, one before the first interval and one after the last, clipped to the outer range the problem gives; Employee Free Time has no outer range, so it reports only the inner gaps.
3169. Count Days Without Meetings
time space
Merge, Then Sum O(2n + n log n) time O(sort + n) aux
Merge While Tracking the Total O(n + n log n) time best time O(sort + n) aux Track Only the Last Interval O(n + n log n) time best time O(sort) aux best space FIG. 3169 COUNT DAYS WITHOUT MEETINGS MERGE THEN SUM ● INTERACTIVE
visualization loads as you reach it
class Solution:
def countDays(self, days: int, meetings: List[List[int]]) -> int:
meetings.sort()
getMerged = lambda a, b: (min(a[0], b[0]), max(a[1], b[1]))
doesIntersect = lambda a, b: (a[1] >= b[0] and b[1] >= a[0])
getGap = lambda a, b: (max(a[0], b[0]) - min(a[1], b[1]) - 1)
merged = [[0, 0]]
for inter in meetings:
if merged and doesIntersect(merged[-1], inter):
merged[-1] = getMerged(merged[-1], inter)
else:
merged.append(inter)
merged.append([days + 1, days + 1])
no_meeting_days = 0
for i in range(len(merged) - 1):
no_meeting_days += getGap(merged[i], merged[i + 1])
return no_meeting_days
How it worksO(2n + n log n) · O(sort + n)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - top ↑
2655. Find Maximal Uncovered Ranges
2655 Find Maximal Uncovered Ranges LeetCode time space
Merge, Then Find Gaps O(2n + n log n) time O(sort + n) aux
Merge While Recording Gaps O(n + n log n) time best time O(sort + n) aux Track Only the Last Interval O(n + n log n) time best time O(sort) aux best space FIG. FIND MAXIMAL UNCOVERED RANGES MERGE THEN FIND GAPS ● INTERACTIVE
visualization loads as you reach it
class Solution:
def findMaximalUncoveredRanges(self, n: int, ranges: List[List[int]]) -> List[List[int]]:
ranges.sort()
getMerged = lambda a, b: (min(a[0], b[0]), max(a[1], b[1]))
doesIntersect = lambda a, b: a[1] >= b[0] and b[1] >= a[0]
merged = [[-1, -1]]
for inter in ranges:
if merged and doesIntersect(merged[-1], inter):
merged[-1] = getMerged(merged[-1], inter)
else:
merged.append(inter)
merged.append([n, n])
ans = []
for i in range(len(merged) - 1):
i, j = merged[i][1] + 1, merged[i + 1][0] - 1
if i <= j:
ans.append([i, j])
return ans
How it worksO(2n + n log n) · O(sort + n)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - top ↑
163. Missing Ranges
time space
Merge, Then Find Gaps O(2n) time O(n) aux
Merge While Recording Gaps O(n) time best time O(n) aux Track Only the Last Interval O(n) time best time O(1) aux best space FIG. 163 MISSING RANGES MERGE THEN FIND GAPS ● INTERACTIVE
visualization loads as you reach it
class Solution:
def findMissingRanges(self, nums: List[int], lower: int, upper: int) -> List[List[int]]:
getMerged = lambda a, b: (min(a[0], b[0]), max(a[1], b[1]))
doesIntersect = lambda a, b: a[1] >= b[0] and b[1] >= a[0]
merged = [[lower - 1, lower - 1]]
for n in nums:
inter = [n - 1, n]
if merged and doesIntersect(merged[-1], inter):
merged[-1] = getMerged(merged[-1], inter)
else:
merged.append(inter)
merged.append([upper, upper])
# print(merged)
ans = []
for i in range(len(merged) - 1):
i, j = merged[i][1] + 1, merged[i + 1][0]
if i <= j:
ans.append([i, j])
return ans
How it worksO(2n) · O(n)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - top ↑
759. Employee Free Time
time space
Merge, Then Find Gaps O(2n + n log n) time O(sort + 2n) aux
Merge While Recording Gaps O(n + n log n) time best time O(sort + 2n) aux Track Only the Last Interval O(n + n log n) time best time O(sort + n) aux best space Line Sweep O(2n + n log n) time O(sort + 2n) aux
FIG. EMPLOYEE FREE TIME MERGE THEN FIND GAPS ● INTERACTIVE
visualization loads as you reach it
class Solution:
def employeeFreeTime(self, schedule: "[[Interval]]") -> "[Interval]":
getMerged = lambda a, b: (min(a[0], b[0]), max(a[1], b[1]))
doesIntersect = lambda a, b: (a[1] >= b[0] and b[1] >= a[0])
getGapInterval = lambda a, b: Interval(min(a[1], b[1]), max(a[0], b[0]))
busy = sorted((sch.start, sch.end) for emp in schedule for sch in emp)
merged = []
for inter in busy:
if merged and doesIntersect(merged[-1], inter):
merged[-1] = getMerged(merged[-1], inter)
else:
merged.append(inter)
ans = []
for i in range(len(merged) - 1):
ans.append(getGapInterval(merged[i], merged[i + 1]))
return ans
How it worksO(2n + n log n) · O(sort + 2n)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - top ↑
3964. Minimum Lights to Illuminate a Road
3964 Minimum Lights to Illuminate a Road LeetCode time space
Merge Illuminated Spans, Fill Gaps O(n + n log n) time O(sort + n) aux
Track Last Interval Only O(n + n log n) time O(sort + n) aux
Line Sweep O(2n + n log n) time O(sort + n) aux
Line Sweep + Counting Sort O(3n) time best time O(n) aux best space
Merge Illuminated Spans, Fill Gaps
FIG. 3964 MINIMUM LIGHTS TO ILLUMINATE A ROAD ● INTERACTIVE
visualization loads as you reach it
class Solution:
def minLights(self, lights: list[int]) -> int:
intervals = []
n = len(lights)
for i, v in enumerate(lights):
if v:
intervals.append([max(0, i - v), min(n - 1, i + v)])
intervals.append([n, n])
intervals.sort()
getMerged = lambda a, b: (min(a[0], b[0]), max(a[1], b[1]))
doesIntersect = lambda a, b: (a[1] >= b[0] and b[1] >= a[0])
getGap = lambda a, b: max(a[0], b[0]) - min(a[1], b[1])
merged = [[-1, -1]]
ans = 0
for inter in intervals:
if merged and doesIntersect(merged[-1], inter):
merged[-1] = getMerged(merged[-1], inter)
else:
gap = getGap(merged[-1], inter) - 1
ans += (gap + 2) // 3
merged.append(inter)
return ans
How it worksO(n + n log n) · O(sort + n)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - top ↑
Cutting a sequence into pieces
Build the intervals first; no sort of the intervals themselves. Each value (or letter) claims the stretch of indices it must stay inside: from its first to its last occurrence, or from where it sits to where it has to end up. Merging those stretches leaves the smallest blocks the sequence can be cut into, and you build them in one pass over the input, so the intervals themselves are never sorted. The answer is read off the blocks: their sizes, their count, or the number of ways to cut between them.
763. Partition Labels
time space
Merge Overlapping Intervals O(n + 26) time O(52) aux
Track Last Interval Only O(n + 26) time O(26) aux
Merge Overlapping Intervals
FIG. 763 PARTITION LABELS ● INTERACTIVE
visualization loads as you reach it
class Solution:
def partitionLabels(self, s: str) -> list[int]:
hm = {}
for idx, char in enumerate(s):
if char not in hm:
hm[char] = [idx, idx]
else:
hm[char][1] = idx
getMerged = lambda a, b: (min(a[0], b[0]), max(a[1], b[1]))
doesIntersect = lambda a, b: a[1] >= b[0] and b[1] >= a[0]
getSize = lambda a: a[1] - a[0] + 1
merged = []
ans = []
for inter in hm.values():
if merged and doesIntersect(merged[-1], inter):
merged[-1] = getMerged(merged[-1], inter)
ans[-1] = getSize(merged[-1])
else:
merged.append(inter)
ans.append(getSize(merged[-1]))
return ans
How it worksO(n + 26) · O(52)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - top ↑
2963. Count the Number of Good Partitions
2963 Count the Number of Good Partitions LeetCode time space
Merge, Then Count the List O(2n + log n) time O(2n) aux
Track Last Interval Only O(2n + log n) time O(n) aux best space
Merge, Then Count the List
FIG. 2963 COUNT THE NUMBER OF GOOD PARTITIONS ● INTERACTIVE
visualization loads as you reach it
class Solution:
def numberOfGoodPartitions(self, nums: List[int]) -> int:
hm = {}
for idx, num in enumerate(nums):
if num not in hm:
hm[num] = [idx, idx]
else:
hm[num][1] = idx
getMerged = lambda a, b: (min(a[0], b[0]), max(a[1], b[1]))
doesIntersect = lambda a, b: a[1] >= b[0] and b[1] >= a[0]
merged = []
for inter in hm.values():
if merged and doesIntersect(merged[-1], inter):
merged[-1] = getMerged(merged[-1], inter)
else:
merged.append(inter)
return pow(2, len(merged) - 1, 10**9 + 7)
How it worksO(2n + log n) · O(2n)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - top ↑
769. Max Chunks To Make Sorted
time space
Merge, Then Count the List O(2n) time O(2n) aux
Merge While Building O(n) time best time O(n) aux Track Last Interval Only O(n) time best time O(1) aux best space
Merge, Then Count the List
FIG. 769 MAX CHUNKS TO MAKE SORTED ● INTERACTIVE
visualization loads as you reach it
class Solution:
def maxChunksToSorted(self, arr: list[int]) -> int:
intervals = []
for idx, num in enumerate(arr):
intervals.append((min(idx, num), max(idx, num)))
getMerged = lambda a, b: (min(a[0], b[0]), max(a[1], b[1]))
doesIntersect = lambda a, b: a[1] >= b[0] and b[1] >= a[0]
merged = []
for inter in intervals:
if merged and doesIntersect(merged[-1], inter):
merged[-1] = getMerged(merged[-1], inter)
else:
merged.append(inter)
return len(merged)
How it worksO(2n) · O(2n)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - top ↑
768. Max Chunks To Make Sorted II
time space
Merge, Then Count the List O(2n + 2n log n) time O(sort + 4n) aux
Merge While Building O(n + 2n log n) time best time O(sort + 3n) aux Track Last Interval Only O(n + 2n log n) time best time O(sort + 2n) aux best space
Merge, Then Count the List
class Solution:
def maxChunksToSorted(self, arr: list[int]) -> int:
sorted_arr = sorted(arr)
hm = {}
intervals = []
for idx, num in enumerate(arr):
if num not in hm:
hm[num] = bisect.bisect_left(sorted_arr, num) - 1
hm[num] += 1
intervals.append((min(hm[num], idx), max(hm[num], idx)))
getMerged = lambda a, b: (min(a[0], b[0]), max(a[1], b[1]))
doesIntersect = lambda a, b: a[1] >= b[0] and b[1] >= a[0]
merged = []
for inter in intervals:
if merged and doesIntersect(merged[-1], inter):
merged[-1] = getMerged(merged[-1], inter)
else:
merged.append(inter)
return len(merged)
How it worksO(2n + 2n log n) · O(sort + 4n)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - top ↑
Two sorted lists
No merge: two pointers, advance the earlier end. Both lists are already sorted and internally disjoint, so emit the shared stretch of the current pair if there is one, then advance whichever interval ends first, because it can never intersect anything later.
986. Interval List Intersections
FIG. 986 INTERVAL LIST INTERSECTIONS ● INTERACTIVE
visualization loads as you reach it
class Solution:
def intervalIntersection(
self, firstList: List[List[int]], secondList: List[List[int]]
) -> List[List[int]]:
common = []
getIntersection = lambda a, b: (max(a[0], b[0]), min(a[1], b[1]))
doesIntersect = lambda a, b: (a[1] >= b[0] and b[1] >= a[0])
i = j = 0
while i < len(firstList) and j < len(secondList):
a, b = firstList[i], secondList[j]
if doesIntersect(a, b):
inter = getIntersection(a, b)
common.append(inter)
if a[1] < b[1]:
i += 1
else:
j += 1
return common
How it worksO(n + m) · O(1)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - top ↑