Skills for tackling LeetCode problems - Merge Intervals.
This approach is usually used to deal with overlapping intervals. Given two intervals (‘a’ and ‘b’) there are SIX different ways the two intervals can relate to each other:
- ‘a’ and ‘b’ do not overlap. ‘a’ lies in front of ‘b’.
- ‘a’ and ‘b’ do not overlap. ‘b’ lies in front of ‘a’.
- ‘a’ completely overlaps ‘b’. (‘b’ is a subset of ‘a’)
- ‘b’ completely overlaps ‘a’. (‘a’ is a subset of ‘b’)
- ‘a’ and ‘b’ overlap. ‘b’ starts before ‘a’ ends, and ends after ‘a’ ends.
- ‘a’ and ‘b’ overlap. ‘a’ starts before ‘b’ ends, and ends after ‘b’ ends.
1. Merge Intervals
Given a list of intervals, merge all the overlapping intervals to produce a list that has only mutually exclusive intervals.
# Time complexity O(N Log N), where N is the number of intervals. It takes O(N) to iterate through the intervals. It takes O(N log N) to sort.
# Space complexity O(N).
# class Interval:
# def __init__(self, start, end):
# self.start = start
# self.end = end
def merge(intervals):
if len(intervals) < 2: # nothing to merge for one interval
return intervals
intervals.sort(key= lambda x: x.start) # sort the intervals on the start point
result = []
start = intervals[0].start
end = intervals[0].end
for i in range(1, len(intervals)):
interval = intervals[i]
if interval.start <= end: # overlap
end = max(interval.end, end)
else: # not overlap
result.append(Interval(start,end))
# if it does not overlap this interval, it canont overlap next one
# so move to the next interval
start = interval.start
end = interval.end
# add the last interval
result.append(Interval(start, end))
return result
Given a set of intervals, find out if any two intervals overlap.
def if_overlap(intervals): # input is a list of lists
if len(intervals) < 2:
return False
intervals.sort(key = lambda x: x[0])
start = intervals[0][0]
end = intervals[0][-1]
for i in range(1, len(intervals)):
interval = intervals[i]
if interval[0] <= end:
print("Intervals ["+ str(start) + ", "+ str(end) + "] and [" + str(interval[0]) + ", " + str(interval[-1]) + "] overlap.")
return True
else:
start = interval[0]
end = interval[-1]
return False
2. Insert Interval
Given a list of non-overlapping intervals by their start time, insert a given interval at the correct position and merge all necessary intervals to produce a list that has only mutually exclusive intervals.
# Time complexity O(N), where N is the number of intervals.
# Space complexity O(N).
def insert(intervals, new_interval):
result = []
end_index = 0
for i in range(len(intervals)):
if intervals[i][-1] < new_interval[0]: # intervals before the new intervals. No overlap.
result.append([intervals[i]])
end_index += 1 # count number of intervals before the new one
elif intervals[i][0] <= new_interval[-1]: # overlap, merge
new_interval[0] = min(intervals[i][0], new_interval[0])
new_interval[-1] = max(intervals[i][-1], new_interval[-1])
end_index += 1 # continue count the numebr of intervals merged with the new one
else:
break
result.append(new_interval) # append the merged new interval
for i in range(end_index, len(intervals)):
result.append(intervals[i]) # append the remaining intervals
return result
3. Intervals Intersection
Given two lists of intervals, find the intersection of these two lists. Each lists consists of disjoint intervals sorted on their start time.
# Time complexity O(N+M). Space complexity O(1)
def merge(intervals_a, intervals_b):
result = []
i, j = 0,0
# Iterate through both lists to see if any two intervals overlap
while i < len(intervals_a) and j < len(intervals_b):
# a interval starts within the b interval
case1 = intervals_a[i][0] >= intervals_b[j][0] and intervals_a[i][0] <= intervals_b[j][-1]
# b interval starts within the a interval
case2 = intervals_b[j][0] >= intervals_a[i][0] and intervals_b[j][0] <= intervals_a[i][-1]
if case1 or case2:
# find intersection
result.append([max(intervals_a[i][0], intervals_b[j][0]), min(intervals_a[i][-1], intervals_b[j][-1])])
# move next from the interval which is finishing first.
if intervals_a[i][-1] < intervals_b[j][-1]:
i += 1
else:
j += 1
return result
4. Conflicting Appointment
Given an array of intervals representing “N” appointments, find out if a person can attend all the appointments.
# Time complexity O(N log N). Iterating takes O(N). Sorting takes O(N log N)
# Space complexity O(N) required for sorting. Note that arrays.sort() uses Timsort which need O(N) space.
def can_attend_all_appointments(intervals):
intervals.sort(key = lambda x: x[0])
for i in range(1, len(intervals)):
if intervals[i][0] < intervals[i-1][-1]: # overlap
return False # conflict
return True