🧠
Brute Force (Pure Recursion / Nested Loops / etc.)
💡 Starting with brute force helps understand the problem deeply by exploring all possible subsets of intervals, even though it's inefficient.
Intuition
Try every interval and decide whether to include it or not, ensuring no overlaps with previously chosen intervals.
Algorithm
- Sort intervals by start time to have a consistent order.
- Use recursion to explore two choices for each interval: include it if it doesn't overlap with the last chosen, or skip it.
- Keep track of the end time of the last included interval to check for overlaps.
- Return the maximum count obtained from all recursive paths.
💡 This approach is hard because it explores all subsets, which grows exponentially, but it clearly shows the problem's combinatorial nature.
from typing import List
def max_non_overlapping_intervals(intervals: List[List[int]]) -> int:
intervals.sort(key=lambda x: x[0])
n = len(intervals)
def backtrack(index: int, last_end: int) -> int:
if index == n:
return 0
# Skip current interval
skip = backtrack(index + 1, last_end)
take = 0
if intervals[index][0] >= last_end:
take = 1 + backtrack(index + 1, intervals[index][1])
return max(skip, take)
return backtrack(0, float('-inf'))
# Driver code
if __name__ == '__main__':
intervals = [[1,3],[2,4],[3,5]]
print(max_non_overlapping_intervals(intervals)) # Output: 2
Line Notes
intervals.sort(key=lambda x: x[0])Sort intervals by start time to process them in order
def backtrack(index: int, last_end: int) -> int:Define recursive function to explore choices at each interval
if index == n:Base case: no more intervals to process
skip = backtrack(index + 1, last_end)Option 1: skip current interval and move forward
if intervals[index][0] >= last_end:Check if current interval can be taken without overlap
take = 1 + backtrack(index + 1, intervals[index][1])Option 2: take current interval and update last_end
return max(skip, take)Choose the better option between taking or skipping
return backtrack(0, float('-inf'))Start recursion with no intervals chosen yet
import java.util.*;
public class Main {
public static int maxNonOverlappingIntervals(int[][] intervals) {
Arrays.sort(intervals, Comparator.comparingInt(a -> a[0]));
return backtrack(intervals, 0, Integer.MIN_VALUE);
}
private static int backtrack(int[][] intervals, int index, int lastEnd) {
if (index == intervals.length) return 0;
int skip = backtrack(intervals, index + 1, lastEnd);
int take = 0;
if (intervals[index][0] >= lastEnd) {
take = 1 + backtrack(intervals, index + 1, intervals[index][1]);
}
return Math.max(skip, take);
}
public static void main(String[] args) {
int[][] intervals = {{1,3},{2,4},{3,5}};
System.out.println(maxNonOverlappingIntervals(intervals)); // Output: 2
}
}
Line Notes
Arrays.sort(intervals, Comparator.comparingInt(a -> a[0]))Sort intervals by start time for ordered processing
private static int backtrack(int[][] intervals, int index, int lastEnd)Recursive helper to explore choices at each interval
if (index == intervals.length) return 0;Base case: no intervals left
int skip = backtrack(intervals, index + 1, lastEnd);Option to skip current interval
if (intervals[index][0] >= lastEnd)Check if current interval can be taken without overlap
take = 1 + backtrack(intervals, index + 1, intervals[index][1]);Option to take current interval and update lastEnd
return Math.max(skip, take);Return the better choice
System.out.println(maxNonOverlappingIntervals(intervals));Print result for given input
#include <bits/stdc++.h>
using namespace std;
int backtrack(vector<vector<int>>& intervals, int index, int lastEnd) {
if (index == (int)intervals.size()) return 0;
int skip = backtrack(intervals, index + 1, lastEnd);
int take = 0;
if (intervals[index][0] >= lastEnd) {
take = 1 + backtrack(intervals, index + 1, intervals[index][1]);
}
return max(skip, take);
}
int maxNonOverlappingIntervals(vector<vector<int>>& intervals) {
sort(intervals.begin(), intervals.end(), [](auto& a, auto& b) { return a[0] < b[0]; });
return backtrack(intervals, 0, INT_MIN);
}
int main() {
vector<vector<int>> intervals = {{1,3},{2,4},{3,5}};
cout << maxNonOverlappingIntervals(intervals) << "\n"; // Output: 2
return 0;
}
Line Notes
sort(intervals.begin(), intervals.end(), [](auto& a, auto& b) { return a[0] < b[0]; });Sort intervals by start time for consistent order
int backtrack(vector<vector<int>>& intervals, int index, int lastEnd)Recursive function to explore choices
if (index == (int)intervals.size()) return 0;Base case: no intervals left
int skip = backtrack(intervals, index + 1, lastEnd);Option to skip current interval
if (intervals[index][0] >= lastEnd)Check if current interval can be taken without overlap
take = 1 + backtrack(intervals, index + 1, intervals[index][1]);Option to take current interval
return max(skip, take);Return the best choice
cout << maxNonOverlappingIntervals(intervals) << "\n";Print the result
function maxNonOverlappingIntervals(intervals) {
intervals.sort((a, b) => a[0] - b[0]);
const n = intervals.length;
function backtrack(index, lastEnd) {
if (index === n) return 0;
const skip = backtrack(index + 1, lastEnd);
let take = 0;
if (intervals[index][0] >= lastEnd) {
take = 1 + backtrack(index + 1, intervals[index][1]);
}
return Math.max(skip, take);
}
return backtrack(0, -Infinity);
}
// Driver code
const intervals = [[1,3],[2,4],[3,5]];
console.log(maxNonOverlappingIntervals(intervals)); // Output: 2
Line Notes
intervals.sort((a, b) => a[0] - b[0]);Sort intervals by start time for ordered processing
function backtrack(index, lastEnd)Recursive helper to explore choices
if (index === n) return 0;Base case: no intervals left
const skip = backtrack(index + 1, lastEnd);Option to skip current interval
if (intervals[index][0] >= lastEnd)Check if current interval can be taken without overlap
take = 1 + backtrack(index + 1, intervals[index][1]);Option to take current interval
return Math.max(skip, take);Return the better choice
console.log(maxNonOverlappingIntervals(intervals));Print result for given input
TimeO(2^n)
SpaceO(n) recursion stack
We explore all subsets of intervals, which is exponential in n. Each interval has two choices: take or skip.
💡 For n=20, this means over a million recursive calls, which is impractical.
Interview Verdict: TLE
This approach is too slow for large inputs but is useful to understand the problem's exhaustive nature.