The problem
Given an array of 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.
Examples
Input: intervals = [[1,2],[2,3],[3,4],[1,3]]
Output: 1
Explanation: [1,3] can be removed and the rest of the intervals are non-overlapping.
Input: intervals = [[1,2],[1,2],[1,2]]
Output: 2
Explanation: You need to remove two [1,2] to make the rest of the intervals non-overlapping.
Input: intervals = [[1,2],[2,3]]
Output: 0
Explanation: You don't need to remove any of the intervals since they're already non-overlapping.
Constraints
- 1 <= intervals.length <= 10^5
- intervals[i].length == 2
- -5 * 10^4 <= starti < endi <= 5 * 10^4
Explanation
Before we dive into coding, let’s figure out the definition of overlapping intervals for this problem.
- The intervals with values
[1, 2]
and[3, 4]
are not considered overlapping - Next, the intervals with values
[1, 3]
and[2, 4]
are considered overlapping - Lastly, the intervals with values
[1,2]
and[2,3]
are not considered overlapping. If they have the same edge point, they are not counted as overlapping.
Recursion Solution
func eraseOverlapIntervals(_ intervals: [[Int]]) -> Int {
var intervals = intervals
intervals.sort(by: { $0[0] < $1[0] })
let n = intervals.count
func dfs(_ i: Int, _ prev: Int) -> Int {
if i == n {
return 0
}
var res = dfs(i + 1, prev)
if prev == -1 || intervals[prev][1] <= intervals[i][0] {
res = max(res, 1 + dfs(i + 1, i))
}
return res
}
return n - dfs(0, -1)
}
Explanation
We need to visualize the problem to be able to solve it. Let’s look at the first example with intervals = [[1,2],[2,3],[3,4],[1,3]]
We can see that intervals [1,2],[2,3],[3,4]
are not overlapping, but when you insert interval [1,3]
they become overlapping.
Now we want to eliminate intervals, and we want to eliminate the minimum
number:
- We can remove only interval
[1, 3]
, but it’s not the only way we could do it - We could remove intervals
[1,2]
and[2,3]
, but that’s not what we are looking for
The brute force way to solve this problem is to go through every single combination, where we can choose to remove an interval or keep an interval.
If we have two choices for every single interval inside a list of intervals, the time complexity to check every single possibility would be O(2^n). That is not very efficient and we can do better.
Time/ Space complexity
- Time complexity: O(2^n)
- Space complexity: O(n)
Dynamic Programming Top-Down Solution
func eraseOverlapIntervals(_ intervals: [[Int]]) -> Int {
var intervals = intervals
intervals.sort(by: { $0[1] < $1[1] })
let n = intervals.count
var memo: [Int: Int] = [:]
func dfs(_ i: Int) -> Int {
if memo[i] != nil {
return memo[i]!
}
var res = 1
for j in i + 1 ..< n {
if intervals[i][1] <= intervals[j][0] {
res = max(res, 1 + dfs(j))
break
}
}
memo[i] = res
return res
}
return n - dfs(0)
}
Explanation
In the previous solution, we learned that we can optimize the recursive solution. We can do it by caching it.
The way this works:
- We would add an additional hash map where we would store index and the number of intervals that we need to remove as value, so this way we would eliminate repeated work and improve our time complexity.
We can go even further and get O(n) time complexity.
Time/ Space complexity
- Time complexity: O(n^2)
- Space complexity: O(n)
Greedy Sort By Start Solution
func eraseOverlapIntervals(_ intervals: [[Int]]) -> Int {
let intervals = intervals.sorted(by: { $0[0] < $1[0] })
var res = 0
var prevEnd = intervals[0][1]
for interval in intervals.dropFirst() {
let start = interval[0]
let end = interval[1]
if start >= prevEnd {
prevEnd = end
} else {
res += 1
}
prevEnd = min(prevEnd, end)
}
return res
}
Explanation
Before we dive into the solution, we need to remember that the order of intervals that we are given can be random, therefore our first step would be to sort our input by start position.
- After that, we are going to iterate through the sorted input and compare adjacent pairs of intervals
Next, we would check if intervals are overlapping by comparing the end
of the first interval and the start
of the second interval
- If the second interval
starts
after the first oneends
, then they are not overlapping - But if the second interval
starts
before the first oneends
, then they are overlapping
Lastly, we are going to remove the interval that ends first
Time/ Space complexity
- Time complexity: O(n)
- Space complexity: O(n)