436. Find Right Interval
Problem Description
You are given an array of intervals where each interval is represented as intervals[i] = [start_i, end_i]
. Each starting point start_i
is unique across all intervals.
For each interval i
, you need to find its "right interval". A right interval for interval i
is defined as an interval j
that satisfies two conditions:
- The start point of interval
j
must be greater than or equal to the end point of intervali
(i.e.,start_j >= end_i
) - Among all intervals satisfying condition 1, interval
j
should have the minimum possible start point
Note that an interval can be its own right interval if its start point is greater than or equal to its own end point.
Your task is to return an array where each element at index i
contains the index of the right interval for intervals[i]
. If no right interval exists for a particular interval, put -1
at that position in the result array.
For example, if you have intervals [[1,2], [2,3], [0,1]]
:
- For interval
[1,2]
at index 0, its right interval is[2,3]
at index 1 (since 2 >= 2) - For interval
[2,3]
at index 1, there's no interval with start >= 3, so the answer is -1 - For interval
[0,1]
at index 2, its right interval is[1,2]
at index 0 (since 1 >= 1)
The output would be [1, -1, 0]
.
Intuition
The key insight is that we need to efficiently find, for each interval's end point, the smallest start point that is greater than or equal to it. This naturally suggests a searching problem.
If we had all the start points sorted in ascending order, we could use binary search to quickly find the smallest start point that meets our criteria. For any given end point ed
, we want to find the leftmost position in the sorted array where a start point is >= ed
.
However, there's a complication: we need to return the original indices of the intervals, not just the start points. So we can't just sort the start points alone - we need to keep track of which interval each start point belongs to.
This leads us to the approach of creating pairs of (start_point, original_index)
and sorting these pairs by start point. Now when we search for a right interval:
- We take each interval's end point
- Use binary search on our sorted array to find the first start point
>= end
- If found, we retrieve the original index that was paired with that start point
- If not found (binary search returns a position beyond the array), we return
-1
The binary search specifically looks for the leftmost position where we could insert (ed, -inf)
while maintaining sorted order. Using -inf
as the second element ensures that if there are multiple intervals with the same start point as ed
, we get the leftmost one. This gives us the interval with the minimum start point that satisfies our condition.
Solution Approach
Let's walk through the implementation step by step:
Step 1: Create and Sort the Array
First, we create an array arr
that stores pairs of (start_point, original_index)
for each interval:
arr = sorted((st, i) for i, (st, _) in enumerate(intervals))
- We enumerate through
intervals
to get both the indexi
and the interval[st, ed]
- We only care about the start point
st
for sorting, so we ignore the end point using_
- Each element in
arr
becomes a tuple(start_point, original_index)
- The array is sorted by start points in ascending order
Step 2: Initialize the Answer Array
We create an answer array initialized with -1
values:
ans = [-1] * n
This handles the default case where no right interval exists.
Step 3: Find Right Intervals Using Binary Search
For each interval, we find its right interval:
for i, (_, ed) in enumerate(intervals):
j = bisect_left(arr, (ed, -inf))
if j < n:
ans[i] = arr[j][1]
Breaking this down:
- We iterate through the original
intervals
array with its indices - For each interval, we only need its end point
ed
to find the right interval bisect_left(arr, (ed, -inf))
performs binary search to find the leftmost position where we could insert(ed, -inf)
- Since
arr
is sorted by the first element of each tuple, this finds the first interval withstart >= ed
- Using
-inf
as the second element ensures that if there's an interval withstart == ed
, we'll find it
- Since
- The binary search returns an index
j
in the sorted array - If
j < n
, a valid right interval exists:arr[j]
gives us the tuple(start_point, original_index)
arr[j][1]
extracts the original index- We store this original index in
ans[i]
- If
j >= n
, no right interval exists, andans[i]
remains-1
Time Complexity: O(n log n)
where n is the number of intervals
- Sorting takes
O(n log n)
- For each of the n intervals, we perform binary search which takes
O(log n)
- Total:
O(n log n) + O(n * log n) = O(n log n)
Space Complexity: O(n)
for storing the sorted array and answer array
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with a concrete example: intervals = [[3,4], [2,3], [1,2]]
Step 1: Create and Sort the Array
First, we create pairs of (start_point, original_index):
- Interval at index 0:
[3,4]
→(3, 0)
- Interval at index 1:
[2,3]
→(2, 1)
- Interval at index 2:
[1,2]
→(1, 2)
After sorting by start points:
arr = [(1, 2), (2, 1), (3, 0)]
Step 2: Initialize Answer Array
ans = [-1, -1, -1]
Step 3: Find Right Intervals
For interval at index 0: [3,4]
- End point = 4
- Binary search for
(4, -inf)
inarr
- All start points (1, 2, 3) are < 4, so
bisect_left
returns index 3 - Since 3 >= len(arr), no right interval exists
ans[0]
remains -1
For interval at index 1: [2,3]
- End point = 3
- Binary search for
(3, -inf)
inarr
- The first start point >= 3 is at position 2:
(3, 0)
j = 2
, which is < 3, so we found a valid right intervalarr[2][1] = 0
, soans[1] = 0
- This means interval at index 0
[3,4]
is the right interval for[2,3]
For interval at index 2: [1,2]
- End point = 2
- Binary search for
(2, -inf)
inarr
- The first start point >= 2 is at position 1:
(2, 1)
j = 1
, which is < 3, so we found a valid right intervalarr[1][1] = 1
, soans[2] = 1
- This means interval at index 1
[2,3]
is the right interval for[1,2]
Final Result: [-1, 0, 1]
This tells us:
[3,4]
has no right interval (no interval starts at or after 4)[2,3]
's right interval is[3,4]
(starts at 3, which is >= 3)[1,2]
's right interval is[2,3]
(starts at 2, which is >= 2)
Solution Implementation
1from typing import List
2from bisect import bisect_left
3
4class Solution:
5 def findRightInterval(self, intervals: List[List[int]]) -> List[int]:
6 # Get the number of intervals
7 n = len(intervals)
8
9 # Initialize result array with -1 (no right interval found)
10 result = [-1] * n
11
12 # Create a sorted array of (start_time, original_index) pairs
13 # This allows us to efficiently search for the smallest start time >= end time
14 sorted_starts = sorted((start, idx) for idx, (start, _) in enumerate(intervals))
15
16 # For each interval, find its right interval
17 for idx, (_, end) in enumerate(intervals):
18 # Binary search for the first interval whose start >= current interval's end
19 # Using (end, float('-inf')) ensures we find the leftmost position where start >= end
20 position = bisect_left(sorted_starts, (end, float('-inf')))
21
22 # If a valid position is found within bounds, store the original index
23 if position < n:
24 result[idx] = sorted_starts[position][1]
25
26 return result
27
1class Solution {
2 /**
3 * Finds the right interval for each interval in the given array.
4 * The right interval for interval i is the interval j with the smallest start point
5 * that is greater than or equal to the end point of interval i.
6 *
7 * @param intervals 2D array where each element is [start, end] representing an interval
8 * @return array where result[i] is the index of the right interval for intervals[i], or -1 if none exists
9 */
10 public int[] findRightInterval(int[][] intervals) {
11 int n = intervals.length;
12
13 // Create array to store start points with their original indices
14 // Each element is [startPoint, originalIndex]
15 int[][] startPointsWithIndex = new int[n][2];
16 for (int i = 0; i < n; i++) {
17 startPointsWithIndex[i] = new int[] {intervals[i][0], i};
18 }
19
20 // Sort by start points in ascending order
21 Arrays.sort(startPointsWithIndex, (a, b) -> a[0] - b[0]);
22
23 // Build result array
24 int[] result = new int[n];
25 for (int i = 0; i < n; i++) {
26 // For each interval, find the smallest start point >= current interval's end point
27 int endPoint = intervals[i][1];
28 int index = binarySearchStartPoint(startPointsWithIndex, endPoint);
29
30 // If valid index found, get the original index; otherwise, set to -1
31 result[i] = (index < n) ? startPointsWithIndex[index][1] : -1;
32 }
33
34 return result;
35 }
36
37 /**
38 * Binary search to find the leftmost position where start point >= target value.
39 *
40 * @param startPointsWithIndex sorted array of [startPoint, originalIndex] pairs
41 * @param targetValue the value to search for (end point of an interval)
42 * @return index of the first element with start point >= targetValue, or array length if none exists
43 */
44 private int binarySearchStartPoint(int[][] startPointsWithIndex, int targetValue) {
45 int left = 0;
46 int right = startPointsWithIndex.length;
47
48 // Binary search for leftmost position where startPoint >= targetValue
49 while (left < right) {
50 int mid = (left + right) >> 1; // Equivalent to (left + right) / 2
51
52 if (startPointsWithIndex[mid][0] >= targetValue) {
53 // Found a valid start point, but check if there's a smaller valid one
54 right = mid;
55 } else {
56 // Current start point is too small, search in the right half
57 left = mid + 1;
58 }
59 }
60
61 return left;
62 }
63}
64
1class Solution {
2public:
3 vector<int> findRightInterval(vector<vector<int>>& intervals) {
4 int n = intervals.size();
5
6 // Create a vector of pairs (start_point, original_index) to track indices after sorting
7 vector<pair<int, int>> startPoints;
8 for (int i = 0; i < n; ++i) {
9 startPoints.emplace_back(intervals[i][0], i);
10 }
11
12 // Sort by start points in ascending order
13 sort(startPoints.begin(), startPoints.end());
14
15 // For each interval, find the right interval
16 vector<int> result;
17 for (const auto& interval : intervals) {
18 int endPoint = interval[1];
19
20 // Binary search for the first interval whose start >= current interval's end
21 // Using pair(endPoint, -1) ensures we find the leftmost valid position
22 auto it = lower_bound(startPoints.begin(), startPoints.end(),
23 make_pair(endPoint, -1));
24
25 // Calculate the index in the sorted array
26 int index = it - startPoints.begin();
27
28 // If no valid right interval exists, return -1; otherwise return original index
29 if (index == n) {
30 result.push_back(-1);
31 } else {
32 result.push_back(startPoints[index].second);
33 }
34 }
35
36 return result;
37 }
38};
39
1/**
2 * Finds the right interval for each interval in the given array.
3 * The right interval for an interval i is an interval j such that start[j] >= end[i]
4 * and start[j] is minimized.
5 *
6 * @param intervals - Array of intervals where each interval is [start, end]
7 * @returns Array of indices of right intervals, -1 if no right interval exists
8 */
9function findRightInterval(intervals: number[][]): number[] {
10 const intervalCount: number = intervals.length;
11
12 // Create array of [startTime, originalIndex] pairs for binary search
13 const startIndexPairs: number[][] = Array.from(
14 { length: intervalCount },
15 (_, index) => [intervals[index][0], index]
16 );
17
18 // Sort by start time in ascending order
19 startIndexPairs.sort((a, b) => a[0] - b[0]);
20
21 // For each interval, find the smallest start time >= its end time
22 return intervals.map(([startTime, endTime]) => {
23 let left: number = 0;
24 let right: number = intervalCount;
25
26 // Binary search for the leftmost position where start >= endTime
27 while (left < right) {
28 const mid: number = (left + right) >> 1; // Equivalent to Math.floor((left + right) / 2)
29
30 if (startIndexPairs[mid][0] >= endTime) {
31 // Found a valid start, try to find a smaller one
32 right = mid;
33 } else {
34 // Start is too small, search in the right half
35 left = mid + 1;
36 }
37 }
38
39 // Return the original index if found, otherwise -1
40 return left < intervalCount ? startIndexPairs[left][1] : -1;
41 });
42}
43
Time and Space Complexity
Time Complexity: O(n × log n)
The time complexity is dominated by two main operations:
- Sorting the array
arr
withn
elements, which takesO(n × log n)
time - For each of the
n
intervals, performing a binary search usingbisect_left
on the sorted array, which takesO(log n)
per search, resulting inO(n × log n)
total
Since both operations have the same complexity of O(n × log n)
, the overall time complexity is O(n × log n)
.
Space Complexity: O(n)
The space complexity comes from:
- The
ans
list which storesn
elements:O(n)
- The
arr
list which storesn
tuples of (start, index):O(n)
The total auxiliary space used is O(n) + O(n) = O(n)
, where n
is the length of the intervals.
Common Pitfalls
Pitfall 1: Using Wrong Comparison Value in Binary Search
The Problem: A common mistake is using just the end value for binary search instead of a tuple:
# WRONG approach position = bisect_left(sorted_starts, end) # This won't work correctly!
This fails because sorted_starts
contains tuples (start, index)
, not just integers. Python will attempt to compare an integer with a tuple, which either raises an error or produces incorrect results.
The Solution: Always use a tuple with the same structure for comparison:
# CORRECT approach
position = bisect_left(sorted_starts, (end, float('-inf')))
Using float('-inf')
as the second element ensures that if multiple intervals have the same start time equal to end
, we get the leftmost one.
Pitfall 2: Forgetting to Store Original Indices
The Problem: After sorting the intervals, developers might forget that we need to return the original indices, not the sorted positions:
# WRONG approach
sorted_intervals = sorted(intervals)
# ... binary search ...
result[i] = j # j is the position in sorted array, not original index!
The Solution: Always pair each interval's start point with its original index before sorting:
# CORRECT approach
sorted_starts = sorted((start, idx) for idx, (start, _) in enumerate(intervals))
# ... binary search ...
result[i] = sorted_starts[j][1] # Extract the original index
Pitfall 3: Incorrect Handling of Self-Referencing Intervals
The Problem: Some might think an interval cannot be its own right interval and add extra checks:
# UNNECESSARY complexity if j < n and sorted_starts[j][1] != i: # Wrong - an interval CAN be its own right interval result[i] = sorted_starts[j][1]
The Solution:
The problem states that an interval can be its own right interval if start >= end
. No special handling needed:
# CORRECT approach - let the binary search handle it naturally if j < n: result[i] = sorted_starts[j][1]
For example, interval [3, 2]
has itself as its right interval since 3 >= 2
.
Which of these properties could exist for a graph but not a tree?
Recommended Readings
Binary Search Speedrun For each of the Speedrun questions you will be given a binary search related problem and a corresponding multiple choice question The multiple choice questions are related to the techniques and template s introduced in the binary search section It's recommended that you have gone through at
Sorting Summary Comparisons We presented quite a few sorting algorithms and it is essential to know the advantages and disadvantages of each one The basic algorithms are easy to visualize and easy to learn for beginner programmers because of their simplicity As such they will suffice if you don't know any advanced
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Don’t Miss This!