15% off all plans & roadmaps Use code
See plansEditorial
Core insight
Non-overlapping Intervals · Greedy Patterns
Core Insight for Non-overlapping Intervals
To remove the fewest intervals, we should keep the largest possible set of non-overlapping intervals.
Suppose two intervals conflict with the current selection:
[a, b] and [c, d]If both cannot be kept, retaining the interval with the smaller end value leaves at least as much room for all future intervals. An earlier end cannot reduce the set of intervals that can be selected afterward, while a later end may block additional intervals.
Therefore:
- Sort all intervals by increasing end time.
- Maintain the end of the last interval kept.
- Keep the current interval if its start is at least that end.
- Otherwise, count it as removed.
The invariant is:
After processing any prefix of the end-sorted intervals, the selected intervals are non-overlapping, and among all equally sized valid selections considered so far, the greedy selection finishes as early as possible.
The boundary condition is important:
currentStart >= lastEndThis accepts intervals that touch at an endpoint. An overlap occurs only when:
currentStart < lastEndVisual Description
The algorithm visualizes the intervals as horizontal ranges ordered from top to bottom by increasing right endpoint. A boundary marker, lastEnd, moves from left to right as intervals are accepted. For each interval, its left endpoint is compared with that marker:
- If the left endpoint is at or after
lastEnd, the interval is accepted and the marker moves to its right endpoint. - If the left endpoint is before
lastEnd, the interval conflicts with the current schedule and is counted as removed.
The marker never moves backward. Sorting by end time ensures that each accepted transition preserves the earliest possible finishing boundary for the number of intervals selected.
Deep dive
Full breakdown
Full reasoning, pitfalls, and implementation flow
LeetCode 435: Non-overlapping Intervals Solution & Explanation
Problem Overview
TL;DR: Sort intervals by increasing end time, greedily keep every interval that starts at or after the end of the last kept interval, and return the number of intervals not kept.
In LeetCode 435: Non-overlapping Intervals, the goal is to remove the minimum number of intervals so that every remaining pair is non-overlapping. Two intervals may touch at an endpoint, so an interval starting exactly at the previous end is valid.
For example, [1,3] and [3,6] do not overlap because 3 is a shared endpoint rather than an interior overlap. This popular interview question is an interval scheduling problem, and the optimal solution for interviews uses a greedy choice based on the earliest finishing interval.
Brute Force Approach for Non-overlapping Intervals
A naive approach considers every possible subset of intervals:
- Choose a subset of the input intervals to keep.
- Check whether the selected intervals are pairwise non-overlapping.
- Track the largest valid subset.
- Return:
total intervals - largest valid subset sizeA recursive version can either keep or remove each interval.
Naive Pseudocode
function search(index, selectedIntervals):
if index == number of intervals:
if selectedIntervals is non-overlapping:
update largestValidCount
return
search(index + 1, selectedIntervals) // Remove current interval
add intervals[index] to selectedIntervals
search(index + 1, selectedIntervals) // Keep current interval
remove intervals[index] from selectedIntervalsThe recursion explores 2^n subsets. If each completed subset is checked by comparing its intervals, the total complexity is at least exponential and can approach:
O(2^n * n^2)depending on how the overlap check is implemented.
With n as large as 10^5, this approach cannot finish. It will exceed time limits long before reaching the required input size. The main issue is that it repeatedly evaluates many combinations even though the interval structure allows a local greedy decision.
Algorithm Strategy: Greedy Patterns
- Sort the intervals by their end coordinate in ascending order.
- Set the end boundary of the last accepted interval.
- Scan the sorted intervals from left to right.
- If the current interval starts before the tracked boundary, it overlaps an accepted interval, so remove it.
- Otherwise, accept it and update the boundary to the current interval's end.
- Return the number of removed intervals.
The sorted order ensures that when an overlap occurs, the already accepted interval has the earliest possible end among the intervals being considered. This preserves the most room for future intervals.
Execution Flow
- Sort intervals using their end values.
- For example, intervals ending at
3,4, and7are processed in that order.
- For example, intervals ending at
- Initialize the last accepted end boundary.
- Read the next interval:
- If
start < lastEnd, the interval overlaps the current schedule. - Increment the removal count.
- Do not update
lastEnd.
- If
- If
start >= lastEnd:- Keep the interval.
- Set
lastEnd = end.
- Continue until every interval has been processed.
- Return the removal count.
For example, with:
[[1,4], [2,3], [5,7], [6,8]]After sorting by end:
[[2,3], [1,4], [5,7], [6,8]]The execution is:
- Keep
[2,3];lastEnd = 3. [1,4]starts before3; remove it.- Keep
[5,7];lastEnd = 7. [6,8]starts before7; remove it.- Total removed:
2.
Proof of Correctness
We prove that the greedy algorithm removes the minimum number of intervals.
Claim 1: The selected intervals are always non-overlapping
An interval is selected only when:
start >= lastEndTherefore, its start is at or after the end of the previously selected interval. Since intervals are processed in end-sorted order, every newly selected interval is compatible with the current selection. Thus, the selected set is non-overlapping.
Claim 2: Choosing the earliest ending compatible interval is safe
Consider any point during the scan where multiple intervals could be selected next. Let the greedy algorithm choose interval G, whose end is the smallest among the available choices.
For any other possible next interval X:
G.end <= X.endEvery interval that can be scheduled after X must start at or after X.end. Because G.end is no larger, every interval compatible with X is also compatible with G. Therefore, replacing X with G cannot reduce the number of intervals that can be selected later.
Claim 3: The greedy algorithm keeps the maximum possible number of intervals
By repeatedly selecting the earliest finishing compatible interval, the algorithm makes a safe choice at every step. Claim 2 shows that each choice preserves an optimal continuation. Therefore, the final selected set has maximum possible size.
If the input contains n intervals and the algorithm keeps k of them, then the minimum number that must be removed is:
n - kThus, the algorithm returns the minimum removal count.
Pattern Reuse Notes
The following problems share the same pattern/subpattern:
- LeetCode 2406: Divide Intervals Into Minimum Number of Groups
- LeetCode 57: Insert Interval
- LeetCode 56: Merge Intervals
These problems all organize interval boundaries and maintain scheduling or merging constraints while scanning ordered data. LeetCode 2406 tracks simultaneous active intervals, while LeetCode 57 and LeetCode 56 process intervals in boundary order to preserve or combine compatible ranges. LeetCode 435 uses the same interval-processing foundation but applies the earliest-finish greedy rule to maximize the number of retained intervals.
Feynman coach · Before you peek at the code
Can you explain the core insight in your own words?
If you can explain it before reading code, you’re far more likely to recall it under interview pressure.
Reference Implementation
C++ Solution for LeetCode 435
1class Solution {
2public:
3 int eraseOverlapIntervals(vector<vector<int>>& intervals) {
4 if (intervals.empty()) {
5 return 0;
6 }
7
8 sort(intervals.begin(), intervals.end(), [](const vector<int>& a, const vector<int>& b) {
9 return a[1] < b[1];
10 });
11
12 int removed = 0;
13 int lastEnd = intervals[0][1];
14
15 for (int i = 1; i < static_cast<int>(intervals.size()); ++i) {
16 if (intervals[i][0] < lastEnd) {
17 ++removed;
18 } else {
19 lastEnd = intervals[i][1];
20 }
21 }
22
23 return removed;
24 }
25};This implementation sorts by end time, initializes the boundary with the first interval's end, and counts every later interval whose start is strictly less than that boundary.
Java Solution for LeetCode 435
1class Solution {
2 public int eraseOverlapIntervals(int[][] intervals) {
3 java.util.Arrays.sort(intervals, (a, b) -> Integer.compare(a[1], b[1]));
4
5 int kept = 0;
6 int lastEnd = 0;
7 boolean hasLastEnd = false;
8
9 for (int[] interval : intervals) {
10 int start = interval[0];
11 int end = interval[1];
12
13 if (!hasLastEnd || start >= lastEnd) {
14 kept++;
15 lastEnd = end;
16 hasLastEnd = true;
17 }
18 }
19
20 return intervals.length - kept;
21 }
22}This version counts the number of intervals kept instead of directly counting removals. The final result is:
total intervals - kept intervalsThe hasLastEnd flag handles initialization without relying on an artificial boundary value.
Python Solution for LeetCode 435
1from typing import List, Optional
2
3
4class Solution:
5 def eraseOverlapIntervals(self, intervals: List[List[int]]) -> int:
6 intervals.sort(key=lambda interval: interval[1])
7
8 kept = 0
9 last_end = None
10
11 for start, end in intervals:
12 if last_end is None or start >= last_end:
13 kept += 1
14 last_end = end
15
16 return len(intervals) - keptThis implementation follows the same earliest-finish greedy strategy. It uses None to represent that no interval has been accepted yet, then updates last_end only when an interval is kept.
