Non Overlapping Intervals

Question

Given an array of intervalsĀ intervalsĀ whereĀ intervals[i] = [start_i, end_i], return the minimum number of intervals you need to remove to make the rest of the intervals non-overlapping.

Note: Intervals areĀ non-overlappingĀ even if they have a common point. For example,Ā [1,Ā 3]Ā andĀ [2,Ā 4]Ā are overlapping, butĀ [1,Ā 2]Ā andĀ [2,Ā 3]Ā are non-overlapping.

Example 1:

Input: intervals = [[1,2],[2,4],[1,4]]
Output: 1

Explanation: After [1,4] is removed, the rest of the intervals are non-overlapping.

Example 2:

Input: intervals = [[1,2],[2,4]]

Output: 2

Solution

This is a question where we can apply greedy. Imagine you have the following intervals:

The first thing that we can do is to sort these intervals based on the start number. Otherwise it's hard to be able to know which one came first, which one came second.

Once sorted, you will have something like this:

Pasted image 20240526102525.png

Imagine this will be our array, sorted from the order increasing (display vertically).

All the intervals with smaller start will be first, doesn't matter the end.

When we go through these intervals, we will then need to select a strategy to pick which one.

For example, when comparing this one:

Pasted image 20240526103239.png

We will prioritise the one with lower end first because it can fits more interval in.

Imagine if we have something like this:

Pasted image 20240526103331.png

Chosing the red one would not work because it overlap. Since the question is asking for minimum number,

So:

  • If it's overlap with the previous interval, we choose the one with lower end
  • Else, we keep going and keep track of new previous interval

Implementation

class Solution:
    def eraseOverlapIntervals(self, intervals: List[List[int]]) -> int:
        intervals.sort(key = lambda x: x[0])

        lastInterval = None
        numberOfNotMergable = 0
        
        for interval in intervals:
            if not lastInterval:
                lastInterval = interval
                continue
            
            if self.overlap(interval, lastInterval):
                numberOfNotMergable += 1
                lastInterval = self.pick(interval, lastInterval)
            else:
                lastInterval = interval
        
        return numberOfNotMergable

    def overlap(self, interval, mergedInterval):
        if not interval or not mergedInterval: return False
        
        startA, endA = interval
        startB, endB = mergedInterval
        
        return not (startA >= endB or startB >= endA) 

    def pick(self, interval, lastInterval):
        startA, endA = interval
        startB, endB = lastInterval
        
        if endB < endA: return lastInterval
        return interval

Time complexity: $O(nlogn + n)$
Space complexity: $O(1)$

Note: in the overlap algorithm here, We consider the case that if startA == endB it's not overlap as of the condition from the question.

For example these intervals are valid: [3,5][5,7]