🧠
Optimal Two Heaps Approach
💡 This approach uses two max-heaps to efficiently find the next interval by pairing intervals based on their start and end times, avoiding binary search and enabling O(n log n) time with a different data structure.
Intuition
Use two max-heaps: one for intervals sorted by start time, one for intervals sorted by end time. For each interval popped from end heap, find the interval with the smallest start time ≥ its end by popping from start heap.
Algorithm
- Build two max-heaps: one for start times with indices, one for end times with indices.
- Initialize result array with -1.
- While end heap is not empty, pop the top interval (with max end).
- Pop from start heap intervals with start ≥ popped end, track the one with smallest start.
- Assign the index of that interval as the next interval for the popped interval.
- Push back any popped start intervals not used except the chosen one.
- Return the result array.
💡 The two heaps allow pairing intervals by comparing max end and max start efficiently, avoiding scanning or binary searching for each interval.
import heapq
def find_next_intervals(intervals):
n = len(intervals)
result = [-1] * n
max_start_heap = []
max_end_heap = []
for i, (start, end) in enumerate(intervals):
heapq.heappush(max_start_heap, (-start, i))
heapq.heappush(max_end_heap, (-end, i))
while max_end_heap:
end, end_idx = heapq.heappop(max_end_heap)
end = -end
if not max_start_heap or -max_start_heap[0][0] < end:
continue
# Pop all intervals with start >= end, track the one with smallest start
candidates = []
while max_start_heap and -max_start_heap[0][0] >= end:
start, start_idx = heapq.heappop(max_start_heap)
candidates.append((start, start_idx))
# Find the candidate with smallest start (largest negative start)
best = max(candidates, key=lambda x: x[0]) # max negative start = smallest start
result[end_idx] = best[1]
# Push back all candidates except the chosen one
for c in candidates:
if c != best:
heapq.heappush(max_start_heap, c)
return result
# Example usage
if __name__ == '__main__':
intervals = [[2,3],[3,4],[5,6]]
print(find_next_intervals(intervals)) # Output: [1, 2, -1]
Line Notes
result = [-1] * nInitialize result array with -1 assuming no next interval
max_start_heap = []Heap to store intervals by start time (max heap simulated with negative values)
max_end_heap = []Heap to store intervals by end time (max heap simulated with negative values)
heapq.heappush(max_start_heap, (-start, i))Push negative start to simulate max heap
heapq.heappush(max_end_heap, (-end, i))Push negative end to simulate max heap
end, end_idx = heapq.heappop(max_end_heap)Pop interval with max end time
if not max_start_heap or -max_start_heap[0][0] < end: continueIf max start is less than current end, no next interval possible
while max_start_heap and -max_start_heap[0][0] >= endPop intervals with start ≥ current end to find candidates
best = max(candidates, key=lambda x: x[0])Select candidate with smallest start (largest negative start)
result[end_idx] = best[1]Assign next interval index
for c in candidates: if c != best: heapq.heappush(max_start_heap, c)Push back all other candidates for future matches
import java.util.*;
public class NextIntervalTwoHeaps {
public static int[] findNextIntervals(int[][] intervals) {
int n = intervals.length;
int[] result = new int[n];
Arrays.fill(result, -1);
PriorityQueue<int[]> maxStartHeap = new PriorityQueue<>((a,b) -> b[0] - a[0]);
PriorityQueue<int[]> maxEndHeap = new PriorityQueue<>((a,b) -> b[0] - a[0]);
for (int i = 0; i < n; i++) {
maxStartHeap.offer(new int[]{intervals[i][0], i});
maxEndHeap.offer(new int[]{intervals[i][1], i});
}
while (!maxEndHeap.isEmpty()) {
int[] topEnd = maxEndHeap.poll();
int end = topEnd[0];
int endIdx = topEnd[1];
if (maxStartHeap.isEmpty() || maxStartHeap.peek()[0] < end) {
continue;
}
List<int[]> candidates = new ArrayList<>();
while (!maxStartHeap.isEmpty() && maxStartHeap.peek()[0] >= end) {
candidates.add(maxStartHeap.poll());
}
int[] best = candidates.get(0);
for (int[] c : candidates) {
if (c[0] < best[0]) {
best = c;
}
}
result[endIdx] = best[1];
for (int[] c : candidates) {
if (c != best) {
maxStartHeap.offer(c);
}
}
}
return result;
}
public static void main(String[] args) {
int[][] intervals = {{2,3},{3,4},{5,6}};
System.out.println(Arrays.toString(findNextIntervals(intervals)));
}
}
Line Notes
int[] result = new int[n];Initialize result array
Arrays.fill(result, -1);Fill result with -1 assuming no next interval
PriorityQueue<int[]> maxStartHeap = new PriorityQueue<>((a,b) -> b[0] - a[0]);Max heap for start times
PriorityQueue<int[]> maxEndHeap = new PriorityQueue<>((a,b) -> b[0] - a[0]);Max heap for end times
maxStartHeap.offer(new int[]{intervals[i][0], i});Add start time and index to start heap
maxEndHeap.offer(new int[]{intervals[i][1], i});Add end time and index to end heap
int[] topEnd = maxEndHeap.poll();Pop interval with max end time
if (maxStartHeap.isEmpty() || maxStartHeap.peek()[0] < end)If no start interval ≥ end, continue
while (!maxStartHeap.isEmpty() && maxStartHeap.peek()[0] >= end)Pop intervals with start ≥ end to find candidates
int[] best = candidates.get(0); for (int[] c : candidates) { if (c[0] < best[0]) best = c; }Find candidate with smallest start
result[endIdx] = best[1];Assign next interval index
for (int[] c : candidates) { if (c != best) maxStartHeap.offer(c); }Push back other candidates
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
vector<int> findNextIntervals(vector<vector<int>>& intervals) {
int n = intervals.size();
vector<int> result(n, -1);
priority_queue<pair<int,int>> maxStartHeap;
priority_queue<pair<int,int>> maxEndHeap;
for (int i = 0; i < n; i++) {
maxStartHeap.push({intervals[i][0], i});
maxEndHeap.push({intervals[i][1], i});
}
while (!maxEndHeap.empty()) {
auto [end, endIdx] = maxEndHeap.top(); maxEndHeap.pop();
if (maxStartHeap.empty() || maxStartHeap.top().first < end) {
continue;
}
vector<pair<int,int>> candidates;
while (!maxStartHeap.empty() && maxStartHeap.top().first >= end) {
candidates.push_back(maxStartHeap.top());
maxStartHeap.pop();
}
auto best = candidates[0];
for (auto& c : candidates) {
if (c.first < best.first) {
best = c;
}
}
result[endIdx] = best.second;
for (auto& c : candidates) {
if (c != best) {
maxStartHeap.push(c);
}
}
}
return result;
}
int main() {
vector<vector<int>> intervals = {{2,3},{3,4},{5,6}};
vector<int> res = findNextIntervals(intervals);
for (int i : res) cout << i << ' ';
cout << endl;
return 0;
}
Line Notes
vector<int> result(n, -1);Initialize result vector with -1
priority_queue<pair<int,int>> maxStartHeap;Max heap for start times
priority_queue<pair<int,int>> maxEndHeap;Max heap for end times
maxStartHeap.push({intervals[i][0], i});Push start time and index
maxEndHeap.push({intervals[i][1], i});Push end time and index
auto [end, endIdx] = maxEndHeap.top(); maxEndHeap.pop();Pop interval with max end
if (maxStartHeap.empty() || maxStartHeap.top().first < end)No start interval ≥ end, skip
while (!maxStartHeap.empty() && maxStartHeap.top().first >= end)Pop intervals with start ≥ end to find candidates
auto best = candidates[0]; for (auto& c : candidates) { if (c.first < best.first) best = c; }Find candidate with smallest start
result[endIdx] = best.second;Assign next interval index
for (auto& c : candidates) { if (c != best) maxStartHeap.push(c); }Push back other candidates
class MaxHeap {
constructor() {
this.heap = [];
}
parent(i) { return Math.floor((i - 1) / 2); }
left(i) { return 2 * i + 1; }
right(i) { return 2 * i + 2; }
swap(i, j) { [this.heap[i], this.heap[j]] = [this.heap[j], this.heap[i]]; }
push(val) {
this.heap.push(val);
let i = this.heap.length - 1;
while (i > 0 && this.heap[this.parent(i)][0] < this.heap[i][0]) {
this.swap(i, this.parent(i));
i = this.parent(i);
}
}
pop() {
if (this.heap.length === 0) return null;
const top = this.heap[0];
const end = this.heap.pop();
if (this.heap.length > 0) {
this.heap[0] = end;
this.heapify(0);
}
return top;
}
heapify(i) {
let largest = i;
const left = this.left(i);
const right = this.right(i);
if (left < this.heap.length && this.heap[left][0] > this.heap[largest][0]) largest = left;
if (right < this.heap.length && this.heap[right][0] > this.heap[largest][0]) largest = right;
if (largest !== i) {
this.swap(i, largest);
this.heapify(largest);
}
}
peek() {
return this.heap.length > 0 ? this.heap[0] : null;
}
isEmpty() {
return this.heap.length === 0;
}
}
function findNextIntervals(intervals) {
const n = intervals.length;
const result = new Array(n).fill(-1);
const maxStartHeap = new MaxHeap();
const maxEndHeap = new MaxHeap();
for (let i = 0; i < n; i++) {
maxStartHeap.push([intervals[i][0], i]);
maxEndHeap.push([intervals[i][1], i]);
}
while (!maxEndHeap.isEmpty()) {
const [end, endIdx] = maxEndHeap.pop();
if (maxStartHeap.isEmpty() || maxStartHeap.peek()[0] < end) {
continue;
}
const candidates = [];
while (!maxStartHeap.isEmpty() && maxStartHeap.peek()[0] >= end) {
candidates.push(maxStartHeap.pop());
}
let best = candidates[0];
for (const c of candidates) {
if (c[0] < best[0]) {
best = c;
}
}
result[endIdx] = best[1];
for (const c of candidates) {
if (c !== best) {
maxStartHeap.push(c);
}
}
}
return result;
}
// Example usage
const intervals = [[2,3],[3,4],[5,6]];
console.log(findNextIntervals(intervals)); // Output: [1, 2, -1]
Line Notes
const result = new Array(n).fill(-1);Initialize result array with -1
const maxStartHeap = new MaxHeap();Max heap for start times
const maxEndHeap = new MaxHeap();Max heap for end times
maxStartHeap.push([intervals[i][0], i]);Push start time and index
maxEndHeap.push([intervals[i][1], i]);Push end time and index
const [end, endIdx] = maxEndHeap.pop();Pop interval with max end
if (maxStartHeap.isEmpty() || maxStartHeap.peek()[0] < end)No start interval ≥ end, skip
while (!maxStartHeap.isEmpty() && maxStartHeap.peek()[0] >= end)Pop intervals with start ≥ end to find candidates
let best = candidates[0]; for (const c of candidates) { if (c[0] < best[0]) best = c; }Find candidate with smallest start
result[endIdx] = best[1];Assign next interval index
for (const c of candidates) { if (c !== best) maxStartHeap.push(c); }Push back other candidates
Building heaps takes O(n), each interval is pushed and popped at most once from heaps, total O(n log n). Space is O(n) for heaps and result.
💡 This approach is optimal and uses heaps to efficiently pair intervals, suitable for large inputs.
Interview Verdict: Accepted / Optimal
This is the best approach to implement in interviews for this problem, demonstrating mastery of heaps and interval pairing.