You are given an array of intervals, where each interval is represented as a pair of integers [start, end]
. Your task is to find the minimum number of intervals you need to remove from the array so that the remaining intervals do not overlap.
Two intervals [a, b]
and [c, d]
are said to overlap if a < d
and c < b
. The solution must ensure that no two intervals in the final array overlap. You should not reuse or split intervals; each interval can be removed or kept as a whole.
Constraints:
At first glance, it might seem that we need to check every possible combination of intervals to find the set with no overlaps, but that would be computationally intensive. Brute-force approaches would involve checking all subsets, which is not feasible for large inputs.
To optimize, let's consider the nature of the problem: we want to keep as many non-overlapping intervals as possible (which is equivalent to removing as few as possible). This is similar to the classic "activity selection" problem, where the goal is to select the maximum number of non-overlapping activities.
The key insight is that if we always pick the interval that ends earliest, we leave the most "room" for future intervals, minimizing potential overlaps. This leads us to a greedy approach, which is both efficient and effective for this scenario.
We use a greedy algorithm to solve the problem efficiently. Here's a step-by-step breakdown:
end
, and set it to negative infinity initially.end
(i.e., it doesn't overlap with the previously selected interval).end
to this interval's end time.This approach ensures that we always keep the maximum number of non-overlapping intervals, and thus remove the minimum necessary.
Let's consider the input: intervals = [[1,2],[2,3],[3,4],[1,3]]
[[1,2],[2,3],[1,3],[3,4]]
end = -∞
, remove_count = 0
[1,2]
: start (1) ≥ end (-∞), so keep it. Update end = 2
.[2,3]
: start (2) ≥ end (2), so keep it. Update end = 3
.[1,3]
: start (1) < end (3), so overlap. Increment remove_count = 1
.[3,4]
: start (3) ≥ end (3), so keep it. Update end = 4
.[1,3]
) to make the rest non-overlapping.
The Non-overlapping Intervals problem is elegantly solved using a greedy strategy: always keep the interval that ends earliest, and remove overlapping ones. This approach leverages sorting and a single linear scan, resulting in an efficient O(n log n) solution. The key insight is to minimize removals by maximizing the number of non-overlapping intervals, which aligns with the classic activity selection problem. This makes the solution both simple and powerful for large datasets.
class Solution:
def eraseOverlapIntervals(self, intervals):
if not intervals:
return 0
# Sort intervals by end time
intervals.sort(key=lambda x: x[1])
end = float('-inf')
remove_count = 0
for interval in intervals:
if interval[0] >= end:
end = interval[1]
else:
remove_count += 1
return remove_count
class Solution {
public:
int eraseOverlapIntervals(vector<vector<int>>& intervals) {
if (intervals.empty()) return 0;
sort(intervals.begin(), intervals.end(), [](const vector<int>& a, const vector<int>& b) {
return a[1] < b[1];
});
int end = INT_MIN, remove_count = 0;
for (const auto& interval : intervals) {
if (interval[0] >= end) {
end = interval[1];
} else {
++remove_count;
}
}
return remove_count;
}
};
class Solution {
public int eraseOverlapIntervals(int[][] intervals) {
if (intervals.length == 0) return 0;
Arrays.sort(intervals, (a, b) -> a[1] - b[1]);
int end = Integer.MIN_VALUE, removeCount = 0;
for (int[] interval : intervals) {
if (interval[0] >= end) {
end = interval[1];
} else {
removeCount++;
}
}
return removeCount;
}
}
var eraseOverlapIntervals = function(intervals) {
if (!intervals.length) return 0;
intervals.sort((a, b) => a[1] - b[1]);
let end = -Infinity, removeCount = 0;
for (let interval of intervals) {
if (interval[0] >= end) {
end = interval[1];
} else {
removeCount++;
}
}
return removeCount;
};