Find, for each interval, the interval with the smallest start point greater than or equal to its end point using sorting and binary search.
Problem Restatement
We are given a list of intervals.
Each interval is:
[start, end]For each interval i, we need to find another interval j such that:
intervals[j][0] >= intervals[i][1]In words, interval j must start at or after interval i ends.
Among all valid intervals, we need the one with the smallest start point.
Return the original index of that interval.
If no such interval exists, return -1 for that interval.
The problem also says every start point is unique, and i may equal j. (leetcode.com)
Input and Output
| Item | Meaning |
|---|---|
| Input | intervals, a list of [start, end] pairs |
| Output | A list of indices |
| Right interval condition | start_j >= end_i |
| Tie rule | Choose the interval with the smallest valid start |
| Missing right interval | Return -1 |
| Start points | Unique |
Example function shape:
def findRightInterval(intervals: list[list[int]]) -> list[int]:
...Examples
Example 1:
intervals = [[1, 2]]There is only one interval.
Its end is 2, but no interval starts at or after 2.
Answer:
[-1]Example 2:
intervals = [[3, 4], [2, 3], [1, 2]]For [3, 4], we need a start point at least 4.
No interval has that.
So answer at index 0 is -1.
For [2, 3], we need a start point at least 3.
The interval [3, 4] starts at 3, and its original index is 0.
So answer at index 1 is 0.
For [1, 2], we need a start point at least 2.
The interval [2, 3] starts at 2, and its original index is 1.
Answer:
[-1, 0, 1]Example 3:
intervals = [[1, 4], [2, 3], [3, 4]]For [1, 4], no interval starts at least 4.
For [2, 3], the interval [3, 4] starts at 3, so its index is 2.
For [3, 4], no interval starts at least 4.
Answer:
[-1, 2, -1]First Thought: Check Every Interval
For each interval i, we can scan every interval j.
We check whether:
intervals[j][0] >= intervals[i][1]Among all valid intervals, we keep the one with the smallest start.
This works, but it takes:
O(n^2)because every interval scans all other intervals.
The constraint can be large, so we need a faster lookup.
Key Insight
For every interval, the only value we search for is:
end_iWe need the smallest start point that is greater than or equal to this value.
This is exactly a lower-bound search.
So we can:
- Store all interval starts with their original indices.
- Sort them by start.
- For each interval end, binary search the first start that is at least that end.
For example:
intervals = [[3, 4], [2, 3], [1, 2]]Store starts with original indices:
[(3, 0), (2, 1), (1, 2)]Sort them:
[(1, 2), (2, 1), (3, 0)]Now each query becomes binary search.
Algorithm
Create an array:
starts = []For each interval at index i, append:
(start, i)Sort starts.
Create the answer array:
answer = []For each interval [start, end]:
- Binary search in
startsfor the first pair whose start is at leastend. - If found, append that pair’s original index.
- Otherwise, append
-1.
Python’s bisect_left can do the lower-bound search.
Correctness
The sorted starts array contains every interval start point paired with its original index.
For an interval i, a valid right interval must have:
start_j >= end_iAmong all such intervals, the required answer is the interval with the smallest possible start_j.
Because starts is sorted by start point, the first position with start at least end_i is exactly the smallest valid start.
Binary search finds exactly this first position.
If such a position exists, the stored original index is the correct answer for interval i.
If no such position exists, then every interval starts before end_i, so no right interval exists and the correct answer is -1.
Applying this independently to every interval gives the correct output array.
Complexity
| Metric | Value | Why |
|---|---|---|
| Time | O(n log n) | Sorting plus one binary search per interval |
| Space | O(n) | The sorted start array and answer array |
Here, n is the number of intervals.
Implementation
from bisect import bisect_left
class Solution:
def findRightInterval(self, intervals: list[list[int]]) -> list[int]:
starts = []
for i, (start, end) in enumerate(intervals):
starts.append((start, i))
starts.sort()
answer = []
for start, end in intervals:
pos = bisect_left(starts, (end, -1))
if pos == len(starts):
answer.append(-1)
else:
answer.append(starts[pos][1])
return answerCode Explanation
We first collect all starts with their original indices:
starts.append((start, i))This is necessary because sorting changes the order.
Then we sort:
starts.sort()Now the starts are in ascending order.
For each interval, we binary search using its end:
pos = bisect_left(starts, (end, -1))This finds the first pair whose start is at least end.
The -1 is used as the second tuple field. Since original indices are non-negative, (end, -1) comes before any tuple (end, index), so it correctly finds the first start equal to end when one exists.
If pos is outside the array:
if pos == len(starts):then no interval starts at or after end, so we append -1.
Otherwise, we append the original index stored at that sorted position:
answer.append(starts[pos][1])Testing
def run_tests():
s = Solution()
assert s.findRightInterval([[1, 2]]) == [-1]
assert s.findRightInterval(
[[3, 4], [2, 3], [1, 2]]
) == [-1, 0, 1]
assert s.findRightInterval(
[[1, 4], [2, 3], [3, 4]]
) == [-1, 2, -1]
assert s.findRightInterval(
[[1, 1], [3, 4], [2, 3]]
) == [0, -1, 1]
assert s.findRightInterval(
[[-10, -5], [-3, 0], [1, 2]]
) == [1, 2, -1]
print("all tests passed")
run_tests()Test meaning:
| Test | Why |
|---|---|
| Single interval | Checks missing right interval |
| Sorted backward input | Checks original index recovery |
| Mixed intervals | Checks lower-bound choice |
| Zero-length interval | Checks case where i may equal j |
| Negative values | Checks binary search with negative starts |