Loading problems...
TL;DR: Sort the intervals by start time and use a min-heap to track the end times of active groups; the size of the heap represents the minimum number of groups required.
The LeetCode 2406 problem, "Divide Intervals Into Minimum Number of Groups," asks us to organize a collection of time intervals into the fewest number of non-overlapping groups possible. We are given a set of intervals [start, end]. We must assign every interval to a group such that no two intervals within the same group intersect. Two intervals are considered intersecting if they share any common numbers (e.g., [1, 5] and [5, 8] intersect).
This is a classic scheduling problem often phrased as finding the minimum number of resources (like conference rooms or servers) needed to handle a set of tasks that occur over specific time ranges.
A naive approach to solve Divide Intervals Into Minimum Number of Groups involves maintaining a list of groups, where each group stores the intervals assigned to it.
Pseudo-code:
textsort(intervals) groups = [] for interval in intervals: placed = false for group in groups: last_interval = group.last() if interval.start > last_interval.end: group.add(interval) placed = true break if not placed: groups.add([interval]) return groups.length
Complexity Analysis:
The time complexity is in the worst case. If all intervals overlap (e.g., [1, 10], [2, 10], [3, 10]), we create a new group for every interval. For the -th interval, we might iterate through groups before creating a new one. With constraints allowing up to intervals, an solution requires roughly operations, resulting in a Time Limit Exceeded (TLE) error.
The transition from the brute force approach to the optimal solution relies on optimizing how we search for an available group.
In the brute force method, we iterate through all groups to find one that ends before the current interval starts. However, we don't need to check every group. We only care about the group that finishes earliest.
If the current interval starts after the earliest-ending group finishes, we can reuse that group. If the current interval starts before even the earliest-ending group finishes, it is impossible to reuse any existing group (because all other groups end even later). In this case, we are forced to create a new group.
Therefore, we only need to track the end times of the active groups. Specifically, we need efficient access to the minimum end time. A Min-Heap (Priority Queue) is the perfect data structure for this, allowing us to retrieve the minimum element in and update it in .
Visual Description: Imagine plotting the intervals on a timeline. As we sweep from left to right (processing sorted intervals), the heap represents the "active wavefront" of intervals.

Consider intervals = [[5,10], [6,8], [1,5], [2,3], [1,10]].
Sort: [[1,5], [1,10], [2,3], [5,10], [6,8]]
Initialize: min_heap = []
Process [1, 5]:
5.min_heap = [5] (Size: 1)Process [1, 10]:
start = 1. min_heap.top() = 5.1 > 5? No. Overlap exists.10.min_heap = [5, 10] (Size: 2)Process [2, 3]:
start = 2. min_heap.top() = 5.2 > 5? No. Overlap exists.3.min_heap = [3, 5, 10] (Size: 3) (Note: Heap reorders to maintain min property).Process [5, 10]:
start = 5. min_heap.top() = 3.5 > 3? Yes. No overlap.3 (Reuse the group that ended at 3).10.min_heap = [5, 10, 10] (Size: 3)Process [6, 8]:
start = 6. min_heap.top() = 5.6 > 5? Yes. No overlap.5.8.min_heap = [8, 10, 10] (Size: 3)Result: Heap size is 3. Return 3.
The correctness hinges on the property that min_heap.size() tracks the maximum number of overlapping intervals encountered so far.
By sorting by start time, we ensure that when we consider a new interval, all previously processed intervals started at or before the current time. When we check min_heap.top() < current.start, we are asking: "Is there any group that finished before this new task needs to start?"
If the answer is yes, we greedily take that slot. This is optimal because reusing a group that finished earlier leaves groups that finish later available for future intervals that might start later. If the answer is no (the earliest finishing group is still busy), then every active group is busy. We are mathematically forced to allocate a new group. Thus, the number of groups never exceeds the necessary minimum.
cppclass Solution { public: int minGroups(vector<vector<int>>& intervals) { // Sort intervals by start time sort(intervals.begin(), intervals.end()); // Min-heap to store end times of the groups // priority_queue is max-heap by default, so we use 'greater' for min-heap priority_queue<int, vector<int>, greater<int>> pq; for (const auto& interval : intervals) { int start = interval[0]; int end = interval[1]; // If the group with the earliest end time finishes before // the current interval starts, we can reuse that group. if (!pq.empty() && pq.top() < start) { pq.pop(); } // Add the current interval's end time to the heap // (either extending an existing group or starting a new one) pq.push(end); } // The size of the heap represents the number of active groups required return pq.size(); } };
javaimport java.util.Arrays; import java.util.PriorityQueue; class Solution { public int minGroups(int[][] intervals) { // Sort intervals by start time Arrays.sort(intervals, (a, b) -> Integer.compare(a[0], b[0])); // Min-heap to store end times of active groups PriorityQueue<Integer> pq = new PriorityQueue<>(); for (int[] interval : intervals) { int start = interval[0]; int end = interval[1]; // If the earliest ending group finishes strictly before the current start, // we can reuse that group (remove the old end time). if (!pq.isEmpty() && pq.peek() < start) { pq.poll(); } // Add the new end time to the heap pq.offer(end); } // The size of the heap is the minimum number of groups needed return pq.size(); } }
pythonimport heapq class Solution: def minGroups(self, intervals: List[List[int]]) -> int: # Sort intervals by start time intervals.sort() # Min-heap to store end times min_heap = [] for start, end in intervals: # If the heap is not empty and the earliest ending group # finishes before the current interval starts, reuse it. if min_heap and min_heap[0] < start: heapq.heappop(min_heap) # Push the current interval's end time heapq.heappush(min_heap, end) # The size of the heap corresponds to the number of groups return len(min_heap)
Time Complexity:
Space Complexity:
The Greedy - Interval Merging/Scheduling pattern is highly versatile. Understanding the logic of sorting by start time and managing state (often with a heap or pointers) applies to:
Mastering LeetCode 2406 provides the foundation for solving these related interview questions efficiently.
Why does my solution fail on [1, 5], [5, 8]?
<= instead of < when checking for overlap.[1, 5] and [5, 8] share 5, so they cannot be in the same group.pq.peek() < start.Why do I get a Time Limit Exceeded (TLE)?
Can I sort by end time instead?