LeetCode: Intervals

Intervals Intro
What are Intervals
Numbers!
Intervals IRL
Numbers!
Intervals Application: Intervals
Pattern: Numbers!
Ex: Numbers numbers!!
def wow!(n: int) -> int:
return n+1
57. Insert Interval ::2:: - Medium
Topics: Array
Intro
You are given an array of non-overlapping intervals intervals where intervals[i] = [starti, endi] represent the start and the end of the ith interval and intervals is sorted in ascending order by starti. You are also given an interval newInterval = [start, end] that represents the start and end of another interval. Insert newInterval into intervals such that intervals is still sorted in ascending order by starti and intervals still does not have any overlapping intervals (merge overlapping intervals if necessary). Return intervals after the insertion. Note that you don't need to modify intervals in-place. You can make a new array and return it.
Example Input | Output |
---|---|
intervals = [[1,3],[6,9]], newInterval = [2,5] | [[1,5],[6,9]] |
intervals = [[1,2],[3,5],[6,7],[8,10],[12,16]], newInterval = [4,8] | [[1,2],[3,10],[12,16]] |
Constraints:
0 ≤ intervals.length ≤ 104
intervals[i].length == 2
0 ≤ starti ≤ endi ≤ 105
intervals is sorted by starti in ascending order
newInterval.length == 2
0 ≤ start ≤ end ≤ 105
Abstraction
Given a list of sorted, non-overlapping intervals and a new interval, insert the new interval and merge any overlaps so that the resulting list remains sorted and non-overlapping.
Space & Time Complexity
Solution | Time Complexity | Space Complexity | Time Remark | Space Remark |
---|---|---|---|---|
Bug | Error |
---|---|
Brute Force:
Aspect | Time Complexity | Space Complexity | Time Remarks | Space Remarks |
---|---|---|---|---|
Find the Bug:
Solution 1: Greedy Merge - Intervals/Intervals
def insert(self, intervals: List[List[int]], newInterval: List[int]) -> List[List[int]]:
# Note:
# Iterate over all intervals:
# 1. Interval ends before newInterval starts -> add to list
# 2. Interval overlaps -> merge into newInterval, then add
# 3. Interval starts after newInterval ends -> add to list
# Result -> merged list of intervals
n = len(intervals)
res = []
# to create merged interval
start = newInterval[0]
end = newInterval[1]
i = 0
# 1. Interval ends before newInterval starts -> add to list
while i < n and intervals[i][1] < start:
res.append(intervals[i])
i += 1
# 2. Interval overlaps -> merge into newInterval, then add
while i < n and intervals[i][0] <= end:
# update start and end of merged interval
start = min(start, intervals[i][0])
end = max(end, intervals[i][1])
i += 1
# Append merged interval
res.append([start, end])
# 3. Interval starts after newInterval ends -> add to list
while i < n:
res.append(intervals[i])
i += 1
# overall: time complexity O(n)
# overall: space complexity O(1)
return res
56. Merge Intervals ::1:: - Medium
Topics: Array, Sorting
Intro
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 Input | Output |
---|---|
intervals = [[1,3],[2,6],[8,10],[15,18]] | [[1,6],[8,10],[15,18]] |
intervals = [[1,4],[4,5]] | [[1,5]] |
intervals = [[4,7],[1,4]] | [[1,7]] |
Constraints:
1 ≤ intervals.length ≤ 104
intervals[i].length == 2
0 ≤ starti ≤ endi ≤ 105
Abstraction
Given a list of non sorted, overlapping intervals, merge all overlapping intervals.
Space & Time Complexity
Solution | Time Complexity | Space Complexity | Time Remark | Space Remark |
---|---|---|---|---|
Bug | Error |
---|---|
Brute Force:
Aspect | Time Complexity | Space Complexity | Time Remarks | Space Remarks |
---|---|---|---|---|
Find the Bug:
Solution 1: Greedy Sort Merge - Intervals/Intervals
def merge(self, intervals: List[List[int]]) -> List[List[int]]:
# Note:
# Greedy / Sorting approach
# 1. Process root -> sort intervals by start time
# 2. Build -> iterate through sorted intervals, merging overlapping ones
# 3. Explore Choices -> if current interval overlaps previous, merge; else, append
# 4. Result -> list of merged, non-overlapping intervals
# Empty check
if not intervals:
return []
# Sort intervals by start time
intervals.sort(key=lambda x: x[0])
# Initialize with first interval
merged = [intervals[0]]
# Iterate over all intervals
for i in range(1, len(intervals)):
# curr interval
current = intervals[i]
# rightmost interval
last_merged = merged[-1]
# if intervals overlap, merge
if current[0] <= last_merged[1]:
last_merged[1] = max(last_merged[1], current[1])
# no overlap, append interval to list
else:
merged.append(current)
# overall: time complexity O(n log n)
# overall: space complexity O(n)
return merged
Solution 2: Greedy Heap Merge - Intervals/Intervals
def merge(self, intervals: List[List[int]]) -> List[List[int]]:
# Note:
# Greedy / Heap approach
# 1. Push all intervals into a min-heap (ordered by start)
# 2. Pop intervals one by one
# 3. Interval does not overlap with end of array, append
# 4. Interval overlaps with end of array, merge
# Result -> List of merged intervals
# Empty check
if not intervals:
return []
# 1. Push all intervals into a min-heap (ordered by start)
heap = [(s, e) for s, e in intervals]
heapq.heapify(heap)
merged = []
# 2. Pop intervals one by one
while heap:
start, end = heapq.heappop(heap)
# 3. Interval does not overlap with end of array, append
if not merged or merged[-1][1] < start:
merged.append([start, end])
# 4. Interval overlaps with end of array, merge
else:
merged[-1][1] = max(merged[-1][1], end)
# overall: time complexity O(n log n) (heapify + n pops)
# overall: space complexity O(n) for merged list
return merged
435. Non Overlapping Intervals ::1:: - Medium
Topics: Array, Dynamic Programming, Greedy, Sorting
Intro
Given an array of intervals intervals where intervals[i] = [starti, endi], return the minimum number of intervals you need to remove to make the rest of the intervals non-overlapping. Note that intervals which only touch at a point are non-overlapping. For example, [1, 2] and [2, 3] are non-overlapping.
Example Input | Output |
---|---|
intervals = [[1,2],[2,3],[3,4],[1,3]] | 1 |
intervals = [[1,2],[1,2],[1,2]] | 2 |
intervals = [[1,2],[2,3]] | 0 |
Constraints:
1 ≤ intervals.length ≤ 105
intervals[i].length == 2
-5 * 104 ≤ starti < endi ≤ 5 * 104
Abstraction
Given a list intervals, determine the minimum number of intervals needed to be removed ot make the intervals non overlapping
Space & Time Complexity
Solution | Time Complexity | Space Complexity | Time Remark | Space Remark |
---|---|---|---|---|
Bug | Error |
---|---|
Brute Force:
Aspect | Time Complexity | Space Complexity | Time Remarks | Space Remarks |
---|---|---|---|---|
Find the Bug:
Solution 1: Greedy Sort By End - Intervals/Intervals
def eraseOverlapIntervals(self, intervals: List[List[int]]) -> int:
# Note:
# Greedy / Sorting approach
# 0. Empty or single interval: no removals needed
# 1. Sort intervals by end time
# 2. Track end of last kept interval
# 3. Iterate through intervals
# 4. If current interval overlaps last, remove it
# 5. If no overlap, update end to end of new interval
# Result -> count of removed intervals
# Empty check
if not intervals:
return 0
# Sort intervals by end time (earliest finishing first)
intervals.sort(key=lambda x: x[1])
# Track end of first interval
end = intervals[0][1]
removals = 0
# Iterate over intervals
for i in range(1, len(intervals)):
# curr interval
start_i, end_i = intervals[i]
# Interval overlaps -> remove
if start_i < end:
removals += 1
# No overlap -> update end to current interval end
else:
end = end_i
# overall: time complexity O(n)
# overall: space complexity O(1)
return removals
Solution 2: Greedy Sort By Start - Intervals/Intervals
def eraseOverlapIntervals(self, intervals: List[List[int]]) -> int:
# Note:
# Greedy / Sorting approach
# 0. Empty or single interval: no removals needed
# 1. Sort intervals by start time
# 2. Track end of last kept interval
# 3. Iterate through intervals
# 4. Overlap with last, keep the interval with smaller end
# 5. No overlap, update end to end of new interval
# Result -> count of removed intervals
# sort
intervals.sort()
res = 0
# end of first interval
prevEnd = intervals[0][1]
# Iterate over intervals
for start, end in intervals[1:]:
# no overlap -> update prevEnd
if start >= prevEnd:
prevEnd = end
# overlap -> remove one interval, keep the one with smaller end
else:
res += 1
prevEnd = min(end, prevEnd)
# overall: time complexity
# overall: space complexity
return res
252. Meeting Rooms ::1:: - Easy
Topics: Array, Dynamic Programming, Greedy, Sorting
Intro
Given an array of meeting time interval objects consisting of start and end times [[start_1,end_1],[start_2,end_2],...] (start_i < end_i) of the intervals non-overlapping. determine if a person could add all meetings to their schedule without any conflicts.
Example Input | Output |
---|---|
intervals = [(0,30),(5,10),(15,20)] | false |
intervals = [(5,8),(9,15)] | false |
Constraints:
0 ≤ intervals.length ≤ 500
0 ≤ intervals[i].start < intervals[i].end ≤ 1000000
Abstraction
Given a list of intervals representing meetings, determine if a person can make it to all meetings without conflicts.
Space & Time Complexity
Solution | Time Complexity | Space Complexity | Time Remark | Space Remark |
---|---|---|---|---|
Bug | Error |
---|---|
Brute Force:
Aspect | Time Complexity | Space Complexity | Time Remarks | Space Remarks |
---|---|---|---|---|
Find the Bug:
Solution 1: Greedy Sort By Start - Intervals/Intervals
def canAttendMeetings(self, intervals: List[Interval]) -> bool:
# Note:
# 1. Sort intervals by start time
# 2. Iterate through sorted intervals
# 3. Curr meeting starts before previous -> conflict
# Result -> Check if conflict exists
# Empty Check
if not intervals:
return True
# Sort intervals by start time
intervals.sort(key=lambda x: x.start)
# Iterate through sorted intervals
for i in range(1, len(intervals)):
# Curr meeting starts before previous -> conflict
if intervals[i].start < intervals[i-1].end:
return False
# overall: time complexity O(n)
# overall: space complexity O(1)
return True
253. Meeting Rooms II ::1:: - Medium
Topics: Array, Dynamic Programming, Greedy, Sorting
Intro
Given an array of meeting time interval objects consisting of start and end times [[start_1,end_1],[start_2,end_2],...] (start_i < end_i), find the minimum number of days required to schedule all meetings without any conflicts.
Example Input | Output |
---|---|
intervals = [(0,40),(5,10),(15,20)] | 2 |
intervals = [(4,9)] | 1 |
Constraints:
0 ≤ intervals.length ≤ 500
0 ≤ intervals[i].start < intervals[i].end ≤ 1000000
Abstraction
Given a list of intervals representing meetings, determine the minimum number of rooms or 'days' required to schedule all the meetings without conflicts.
Space & Time Complexity
Solution | Time Complexity | Space Complexity | Time Remark | Space Remark |
---|---|---|---|---|
Bug | Error |
---|---|
Brute Force:
Aspect | Time Complexity | Space Complexity | Time Remarks | Space Remarks |
---|---|---|---|---|
Find the Bug:
Solution 1: Greedy MinHeap - Intervals/Intervals
def minMeetingRooms(self, intervals: List[Interval]) -> int:
# Note:
# Greedy Min-Heap
# 1. Sort intervals by start time
# 2. MinHeap to track meeting end times
# 3. Iterate over intervals:
# Curr starts after earliest ending -> reuse room (pop heap)
# Curr starts before earliest ending -> new room needed, push curr end time to heap
# Result -> size of heap at end = min rooms required
# Empty Check
if not intervals:
return 0
# Sort intervals by start time
intervals.sort(key=lambda x: x.start)
# MinHeap to track meeting end times
heap = []
for interval in intervals:
# Curr starts after earliest ending -> reuse room (pop heap)
if heap and interval.start >= heap[0]:
heapq.heappop(heap)
# Curr starts before earliest ending -> new room needed, push curr end time to heap
heapq.heappush(heap, interval.end)
# size of heap at end = min rooms required
res = len(heap)
# overall: time complexity O(n)
# overall: space complexity O(1)
return res
1851. Minimum Interval to Include Each Query ::3:: - Hard
Topics: Array, Binary Search, Line Sweep, Sorting, Heap (Priority Queue)
Intro
You are given a 2D integer array intervals, where intervals[i] = [lefti, righti] describes the ith interval starting at lefti and ending at righti (inclusive). The size of an interval is defined as the number of integers it contains, or more formally righti - lefti + 1. You are also given an integer array queries. The answer to the jth query is the size of the smallest interval i such that lefti < = queries[j] < = righti. If no such interval exists, the answer is -1. Return an array containing the answers to the queries.
Example Input | Output |
---|---|
intervals = [[1,4],[2,4],[3,6],[4,4]], queries = [2,3,4,5] | [3,3,1,4] |
intervals = [[2,3],[2,5],[1,8],[20,25]], queries = [2,19,5,22] | [2,-1,4,6] |
Constraints:
1 ≤ intervals.length ≤ 105
1 ≤ queries.length ≤ 105
intervals[i].length == 2
1 ≤ lefti < righti ≤ 107
1 ≤ queries[j] ≤ 107
Abstraction
For a list of intervals and a list of queries, determine for each query the smallest interval that covers it. If no interval covers a query, return -1.
Space & Time Complexity
Solution | Time Complexity | Space Complexity | Time Remark | Space Remark |
---|---|---|---|---|
Bug | Error |
---|---|
Brute Force:
Aspect | Time Complexity | Space Complexity | Time Remarks | Space Remarks |
---|---|---|---|---|
Find the Bug:
Solution 1: Greedy MinHeap 1 - Intervals/Intervals
def minInterval(self, intervals: List[List[int]], queries: List[int]) -> List[int]:
# Note:
# Uses sorted queries with indices for mapping results.
# Note:
# Greedy + Min-Heap
# 1. Sort intervals by start
# 2. Sort queries with original indices
# 3. Iterate queries in ascending order
# 4. Intervals <= query -> push to heap
# 5. Intervals > query -> pop
# 6. record result
# Result -> top of heap is smallest interval covering query, else -1
# Sort intervals by start
intervals.sort(key=lambda x: x[0])
# Sort queries with original indices
sorted_queries = sorted([(q, i) for i, q in enumerate(queries)])
res = [-1] * len(queries)
# MinHeap: (size, end)
heap = []
# pointer for intervals
j = 0
# Iterate queries in ascending order
for q_val, q_idx in sorted_queries:
# push all intervals starting <= query into heap
while j < len(intervals) and intervals[j][0] <= q_val:
start, end = intervals[j]
size = end - start + 1
heapq.heappush(heap, (size, end))
j += 1
# pop intervals ending before query
while heap and heap[0][1] < q_val:
heapq.heappop(heap)
# record result
if heap:
res[q_idx] = heap[0][0]
# overall: time complexity O(n log n + q log n)
# overall: space complexity O(n)
return res
Solution 2: Greedy MinHeap 2 - Intervals/Intervals
def minInterval(self, intervals: List[List[int]], queries: List[int]) -> List[int]:
# Note:
# Uses a defaultdict map for clarity.
# Note:
# Greedy + Min-Heap
# 0. Direct Boundary -> empty inputs handled implicitly
# 1. Sort intervals by start
# 2. Iterate queries in ascending order
# 3. Push intervals whose start <= query into min-heap (ordered by size)
# 4. Remove intervals whose end < query
# 5. Result -> top of heap is smallest interval covering query, else -1
# 6. Map results back to original query order
# Sort intervals by start
intervals.sort(key=lambda x: x[0])
res_map = defaultdict(int)
# MinHeap of (size, end)
min_heap = []
# Pointer for intervals
i = 0
# Iterate queries in ascending order
for q in sorted(queries):
# Push all intervals starting <= query into heap
while i < len(intervals) and intervals[i][0] <= q:
l, r = intervals[i]
heapq.heappush(min_heap, (r - l + 1, r)) # (size, end)
i += 1
# Pop intervals ending before query
while min_heap and min_heap[0][1] < q:
heapq.heappop(min_heap)
# Record result
res_map[q] = min_heap[0][0] if min_heap else -1
# Map results back to original query order
res = [res_map[q] for q in queries]
# overall: time complexity O(n log n + q log n)
# overall: space complexity O(n)
return res
Solution 3: Greedy MinHeap 3 - Intervals/Intervals
def minInterval(self, intervals: List[List[int]], queries: List[int]) -> List[int]:
# Note:
# Uses a hash to avoid recomputation and pushes intervals
# into heap lazily.
soq = sorted(queries)
out = []
intervals.sort(key=lambda x: x[0])
hsh = {}
i=0
pq = []
for q in soq:
if hsh.get(q,-1) != -1:
continue
while i<len(intervals) and q>=intervals[i][0]:
l,r = intervals[i]
heapq.heappush(pq,(r-l+1,r))
i+=1
while pq:
pr,end = pq[0]
if q>end:
heapq.heappop(pq)
else:
hsh[q]=pr
break
return [hsh.get(p,-1) for p in queries]