Non-Overlapping Intervals
1. Problem & Core Objective
Given an array of intervals, return the minimum number to remove so that the rest are pairwise non-overlapping.
[[1,2],[2,3],[3,4],[1,3]] → 1 (remove [1,3])
[[1,2],[1,2],[1,2]] → 2 (keep one)
[[1,2],[2,3]] → 0 (touching is not overlapping here)Constraints: 1 <= intervals.length <= 10^5, intervals[i] = [start_i, end_i] with start_i < end_i, values in [-5×10^4, 5×10^4].
What's actually being tested: the exchange argument, and specifically whether you sort by the right key. Removing the fewest is the same as keeping the most, which is the classic activity-selection problem — and the greedy that keeps the earliest-ending interval is provably optimal while the intuitive "earliest-starting" is not.
2. First-Principles Thought Process
Flip minimise into maximise
"Remove the fewest" is awkward — you'd be choosing what to destroy. Complement it:
answer = n − (largest set of pairwise non-overlapping intervals)Now it's a maximisation over a set you build up, which is a shape greedy algorithms handle. This reframing is the first move, and it's worth saying out loud before any code.
Which one to keep first?
Building left to right, the question at each step is which interval to commit to. Three plausible rules:
| Rule | Correct? |
|---|---|
| earliest start | ✗ |
| shortest length | ✗ |
| earliest end | ✓ |
[[1,10],[2,3],[4,5]] kills the first rule: [1,10] starts earliest, gets kept, and then blocks both short intervals — one kept, two removed. Sorting by end keeps [2,3] and [4,5] — two kept, one removed. Measured: sorting by start gives the wrong count on 379 of 3,000 random inputs.
Shortest-first fails on [[1,5],[4,6],[5,9]]: the shortest is [4,6], which alone blocks both others — one kept. Keeping [1,5] and [5,9] gives two.
Why earliest-end is optimal — the exchange argument
Let
Sbe any optimal solution, and letgbe the interval with the earliest end overall. IfSalready containsg, done. Otherwise letfbe the first interval inSby end time. Sincegends no later thanf, replacingfwithginSkeeps it non-overlapping: everything else inSstarts at or afterf's end, which is at or afterg's end.The swap changes neither the size nor the validity of
S. Repeating it converts any optimal solution into the greedy one without loss — so greedy is optimal.
The intuition behind the formalism: the earliest-ending interval leaves the most room for everything after it, and room is the only resource being consumed.
Then the sweep is trivial
Sort by end. Keep a running end of the last kept interval. Take the next interval iff its start is >= end.
if (a[i][0] >= end) { kept++; end = a[i][1]; }Nothing is stored, nothing is reconsidered.
Touching
LeetCode 435 guarantees start < end, and [1,2] with [2,3] counts as non-overlapping — they share only a boundary point. That makes the test >= rather than >. Say which convention you're assuming; it is the opposite decision from Merge Intervals, where touching intervals do merge.
That asymmetry is not an inconsistency — merging asks "is the union connected?", scheduling asks "can both happen?" — but being able to name the difference is exactly what an interviewer is probing.
3. Solution Paths
Approach 1 — Brute force: every subset
public int eraseOverlapIntervals(int[][] intervals) {
int n = intervals.length, best = 0;
for (int mask = 0; mask < (1 << n); mask++) {
List<int[]> sel = new ArrayList<>();
for (int i = 0; i < n; i++) if ((mask >> i & 1) == 1) sel.add(intervals[i]);
boolean ok = true;
for (int p = 0; p < sel.size() && ok; p++)
for (int q = p + 1; q < sel.size(); q++) {
int[] u = sel.get(p), v = sel.get(q);
if (Math.max(u[0], v[0]) < Math.min(u[1], v[1])) { ok = false; break; }
}
if (ok) best = Math.max(best, sel.size());
}
return n - best;
}- Time
O(2^n · n²)· SpaceO(n)
The reference implementation — checked against the greedy on 3,000 random inputs up to n = 8.
Counter-questions on this approach
⭐ "Why check all pairs rather than sorting the subset and checking neighbours?"
Because the neighbour check is only valid for a specific sort order, and with ties it's ambiguous which comes first. Checking
max(starts) < min(ends)over all pairs is order-independent, which is what a reference implementation needs to be.I originally wrote the sorted-neighbour version and it disagreed with the greedy on inputs containing equal starts — the reference was wrong, not the algorithm. Worth mentioning because "my brute force is the bug" is a real failure mode.
"Explain max(starts) < min(ends)."
Two intervals overlap in more than a point iff their intersection
[max(starts), min(ends)]is non-empty as an open region. Using strict<means touching doesn't count — which is this problem's convention.Switching it to
<=would make touching count as overlapping, which is Merge Intervals's convention. One character, two different problems.
"What's the practical limit?"
About
n = 20. That's fine for a randomized cross-check and useless for the real constraints of10^5.
Approach 2 — DP on the longest chain
public int eraseOverlapIntervals(int[][] intervals) {
int n = intervals.length;
Arrays.sort(intervals, Comparator.comparingInt(a -> a[0]));
int[] dp = new int[n]; // dp[i] = largest set ending with i
int best = 0;
for (int i = 0; i < n; i++) {
dp[i] = 1;
for (int j = 0; j < i; j++)
if (intervals[j][1] <= intervals[i][0]) // j can precede i
dp[i] = Math.max(dp[i], dp[j] + 1);
best = Math.max(best, dp[i]);
}
return n - best;
}- Time
O(n²)· SpaceO(n)
Counter-questions on this approach
⭐ "This is Longest Increasing Subsequence in disguise. Where's the correspondence?"
Exactly.
dp[i]is the longest chain of compatible intervals ending ati, and "compatible" plays the role of "increasing". The sameO(n²)double loop, the samemaxover valid predecessors (Longest Increasing Subsequence).And like LIS, it can be sped up — sort by end, and
dpbecomes non-decreasing, so binary search finds the best predecessor inO(log n). That getsO(n log n), matching the greedy but withO(n)extra space and considerably more code.
"Why does it work with a start sort here when the greedy needs an end sort?"
Because DP doesn't commit to anything. It considers every predecessor
jexplicitly, so it can't be misled by a bad first choice — the sort only has to ensure predecessors appear before successors, and a start sort does that.That's the general trade: greedy needs the right order because it never reconsiders; DP tolerates any valid order because it reconsiders everything. Naming that is more useful than either solution.
"Would you ever write this?"
Only if I couldn't produce the exchange argument. A correct
O(n²)beats a greedy I can't justify — and atn = 10^5it's10^10operations, so it wouldn't actually pass. I'd say that plainly and then find the argument.
Approach 3 — Sort by end, greedy (optimal)
public int eraseOverlapIntervals(int[][] intervals) {
if (intervals.length == 0) return 0;
Arrays.sort(intervals, Comparator.comparingInt(a -> a[1])); // by END
int kept = 1, end = intervals[0][1];
for (int i = 1; i < intervals.length; i++)
if (intervals[i][0] >= end) { // compatible — take it
kept++;
end = intervals[i][1];
}
return intervals.length - kept;
}- Time
O(n log n)· SpaceO(log n)for the sort
Counter-questions on this approach
⭐ "Prove that taking the earliest-ending interval is safe."
Take any optimal solution
Sand letfbe its earliest-ending member. Letgbe the earliest-ending interval overall. Theng's end is<= f's end, so swappinggin forfkeepsSvalid — everything else inSstarts at or afterf's end, hence at or afterg's end.The swap preserves size, so
Sstays optimal. Repeating it at every step turns any optimal solution into the greedy one. Therefore the greedy is optimal.
⭐ "Why not sort by start?"
Because a long interval that starts early gets committed and then blocks everything behind it.
[[1,10],[2,3],[4,5]]: sorting by start keeps[1,10]and removes two; sorting by end keeps[2,3]and[4,5]and removes one.Measured: the start key is wrong on 379 of 3,000 random inputs — about 13%. Frequent enough to be a real bug, rare enough that a few hand-picked tests miss it.
"Why not sort by length?"
Also wrong. On
[[1,5],[4,6],[5,9]]the shortest is[4,6], which overlaps both others — keeping it gives one, while[1,5]and[5,9]give two.Short intervals feel efficient, but length isn't the resource being consumed. What matters is how much of the remaining timeline you give up, and that's determined by the end alone.
"Why does kept start at 1 rather than 0?"
The first interval after sorting is always taken — it ends earliest, so nothing can be a better first choice. Seeding
kept = 1, end = intervals[0][1]and starting the loop ati = 1avoids a sentinel forend.The alternative is
end = Integer.MIN_VALUEwith the loop from 0, which is equivalent. I prefer the seed becauseMIN_VALUEin an interval context invites someone to do arithmetic on it later.
"Why >= and not >?"
Because touching intervals don't conflict in this problem:
[1,2]and[2,3]can both be kept. If the convention were "sharing an endpoint counts as overlapping", it would be>.This is the opposite of Merge Intervals's
<=, and the difference is semantic — merging asks whether the union is connected, scheduling asks whether both can happen.
"Does this need Math.max on end like Merge Intervals does?"
No, and the reason is the sort key. Sorted by end,
intervals[i][1]is non-decreasing, so the newly kept interval always has the largest end so far — assignment andmaxcoincide.In Merge Intervals the sort is by start, which gives no guarantee about ends, so the
maxis mandatory there. Same-looking line, different justification.
4. Why the Optimal Solution Wins
| Approach | Time | Space | Verdict |
|---|---|---|---|
| All subsets | O(2^n · n²) | O(n) | Reference only |
| DP longest chain | O(n²) | O(n) | 10^10 at the limit; the LIS connection is the value |
| Sort by end, greedy | O(n log n) | O(log n) | Five lines, with a proof |
O(n log n) is optimal in the comparison model — the sort is the only superlinear step, and interval scheduling requires knowing the order of the ends.
Write the greedy, and lead with the exchange argument. On this problem the argument is the answer; producing the five lines without it looks like memorisation, and the interviewer's next question will be "why end and not start?"
5. Java Prerequisites
Sorting by the second column
Arrays.sort(intervals, Comparator.comparingInt(a -> a[1])); // by ENDThe one-character difference from a[0] is the entire algorithm. Worth a comment in the code.
Negative values and subtraction comparators
(a, b) -> a[1] - b[1] // values here span [-5e4, 5e4] — no overflow,
// but the habit is dangerous
Comparator.comparingInt(a -> a[1]) // always safeThe constraints here include negatives, which is exactly the setting where a subtraction comparator starts being risky. It happens not to overflow at 10^5 magnitude, and I'd still not write it (04).
Seeding a greedy from the first element
int kept = 1, end = intervals[0][1];
for (int i = 1; i < n; i++) { ... }The same shape as seeding Kadane with nums[0] (Maximum Subarray) — take the first element as given rather than inventing a neutral value.
Subset enumeration for a reference implementation
for (int mask = 0; mask < (1 << n); mask++)
for (int i = 0; i < n; i++)
if ((mask >> i & 1) == 1) { ... }1 << n overflows at n >= 31 (22).
6. Interview Communication Guide
Clarifying questions: Does sharing an endpoint count as overlapping — can I keep both [1,2] and [2,3] (no overlap here, so yes)? Is removing the minimum the same as keeping the maximum (yes, and I'll solve it that way)? Can intervals be identical (yes — all but one must go)? Are values negative (they can be, which rules out subtraction comparators)?
The pitch
"First I'd flip it: removing the fewest is the same as keeping the most pairwise non-overlapping intervals. That's classic activity selection, and it's a maximisation I can build up greedily.
The greedy rule is to always keep the interval that ends earliest among those still compatible. The argument is an exchange: take any optimal solution and look at its earliest-ending member
f. The globally earliest-ending intervalgends no later thanf, so swappinggin forfkeeps the set valid — everything else starts afterf's end, hence afterg's end. Same size, still optimal. Repeat and any optimal solution becomes the greedy one.The intuition is that the earliest end leaves the most room for everything afterwards, and room is the only resource.
I'd flag what doesn't work. Sorting by start lets one long interval like
[1,10]win the first slot and veto every short interval behind it — on[[1,10],[2,3],[4,5]]it removes two instead of one, and I measured it wrong on about 13% of random inputs. Sorting by length also fails: on[[1,5],[4,6],[5,9]]the shortest interval blocks both others.So: sort by end, sweep, take any interval whose start is at or after the last kept end.
>=rather than>because touching is allowed here — note that's the opposite of Merge Intervals, where touching intervals do merge.One thing worth contrasting: Merge Intervals needs
Math.maxwhen extending the end, and this doesn't. That's purely because sorting by end makes the ends non-decreasing, so assignment and max coincide.
O(n log n)from the sort,O(1)extra space."
Edge cases to volunteer:
| Input | Expected | Tests |
|---|---|---|
[[1,10],[2,3],[4,5]] | 1 | Start-sort gives 2 — the key counterexample |
[[1,5],[4,6],[5,9]] | 1 | Length-sort gives 2 |
[[1,2],[2,3]] | 0 | Touching is allowed — needs >= |
[[1,2],[1,2],[1,2]] | 2 | Identical intervals |
[[1,2]] | 0 | Single interval; loop never runs |
[[-5,-1],[-3,0],[0,3]] | 1 | Negative coordinates |
Lead with [[1,10],[2,3],[4,5]]. Naming the input that defeats the start-sort before writing any code is the clearest possible signal that you chose the key deliberately rather than by luck.
7. Follow-Up Questions — Modified Constraints
⭐ "Return which intervals to remove, not just how many."
Record the kept intervals during the sweep and take the complement — or, since sorting reorders things, carry original indices alongside.
O(n)extra space.Note the answer isn't unique:
[[1,2],[1,2]]allows removing either. If a specific tie-break is wanted — smallest indices, say — sort by(end, index)to make it deterministic.
⭐ "What if each interval had a weight and you maximised total weight kept?"
The greedy dies immediately: one heavy interval can be worth more than three light ones, so "ends earliest" is no longer safe. It becomes weighted interval scheduling — sort by end, and
dp[i] = max(dp[i-1], weight[i] + dp[p(i)])wherep(i)is the last interval ending at or beforei's start, found by binary search.O(n log n).That's the canonical example of a greedy that survives only while all items are equally valuable — and it's worth volunteering, because weights are the most natural follow-up an interviewer has.
"What if you had k machines instead of one — keep the most, running k at a time?"
That's
k-track interval scheduling. A greedy still works: sort by end and assign each interval to any machine whose last end is<= its start, tracking machine ends in a min-heap.O(n log k).Related but distinct from Meeting Rooms II, which asks for the minimum
ksuch that everything fits.
"What if the intervals were circular — wrapping around midnight?"
Much harder. The standard approach fixes one interval as "the one containing the wrap point", removes it from consideration, and solves the remaining linear instance — repeated for each candidate, giving
O(n² log n). There's no single sort order because "earliest end" isn't well-defined on a circle.Circularity breaking a greedy is a recurring theme — the same thing happens in Gas Station and House Robber II.
"What if touching counted as overlapping?"
Change
>=to>. Nothing else moves — which is the cleanest demonstration that the convention lives in exactly one comparison.
"What if n were 10^7?"
The sort dominates. Coordinates here are bounded by
5×10^4, so counting-sort the ends into buckets forO(n + range). The sweep itself is already linear andO(1)space.
"Is there an online version — intervals arriving one at a time, decide immediately?"
No optimal one. Deciding without seeing the future is hopeless in the worst case: accept
[1,10]and a thousand short intervals may follow; reject it and nothing does. This is a competitive-analysis problem, and the achievable ratio depends on what's known about interval lengths.Saying "this problem is inherently offline" is a better answer than inventing a heuristic.