1851. Minimum Interval to Include Each Query - Explanation

Problem Link

Description

You are given a 2D integer array intervals, where intervals[i] = [left_i, right_i] represents the ith interval starting at left_i and ending at right_i (inclusive).

You are also given an integer array of query points queries. The result of query[j] is the length of the shortest interval i such that left_i <= queries[j] <= right_i. If no such interval exists, the result of this query is -1.

Return an array output where output[j] is the result of query[j].

Note: The length of an interval is calculated as right_i - left_i + 1.

Example 1:

Input: intervals = [[1,3],[2,3],[3,7],[6,6]], queries = [2,3,1,7,6,8]

Output: [2,2,3,5,1,-1]

Explanation:

  • Query = 2: The interval [2,3] is the smallest one containing 2, it's length is 2.
  • Query = 3: The interval [2,3] is the smallest one containing 3, it's length is 2.
  • Query = 1: The interval [1,3] is the smallest one containing 1, it's length is 3.
  • Query = 7: The interval [3,7] is the smallest one containing 7, it's length is 5.
  • Query = 6: The interval [6,6] is the smallest one containing 6, it's length is 1.
  • Query = 8: There is no interval containing 8.

Constraints:

  • 1 <= intervals.length <= 1000
  • 1 <= queries.length <= 1000
  • 1 <= left_i <= right_i <= 10000
  • 1 <= queries[j] <= 10000


Topics

Recommended Time & Space Complexity

You should aim for a solution as good or better than O(nlogn + mlogm) time and O(n + m) space, where m is the size of the array queries and n is the size of the array intervals.


Hint 1

A brute force approach would be to iterate through each query and, for each query, check all intervals to find the result. This would be an O(m * n) solution. Can you think of a better way? Maybe processing the queries in sorted order could help.


Hint 2

We sort the intervals by start value and process the queries in ascending order. Using a pointer i, we add intervals to a min-heap while their start values are less than or equal to the query, storing their end values and sizes.


Hint 3

The min-heap is ordered by interval size. We remove elements from the heap while the top element’s end value is less than the current query. The result for the query is the top element’s size if the heap is non-empty; otherwise, it is -1.


Company Tags

Please upgrade to NeetCode Pro to view company tags.



Prerequisites

Before attempting this problem, you should be comfortable with:

  • Sorting - Sorting intervals and queries to process them in order
  • Min Heap / Priority Queue - Efficiently retrieving the smallest interval covering a query
  • Sweep Line Algorithm - Processing events (interval starts, ends, queries) in sorted order
  • Segment Trees with Lazy Propagation - Advanced technique for range minimum queries and updates
  • Coordinate Compression - Mapping large coordinate values to smaller indices

1. Brute Force

Intuition

For each query value q, we want to find the smallest interval length among all intervals [l, r] that contain q (meaning l <= q <= r).
If no interval contains q, we return -1.

The brute force idea is very direct:

  • handle queries one by one
  • for a query, scan through every interval
  • whenever an interval covers the query, compute its length r - l + 1
  • keep the minimum length seen

Algorithm

  1. Initialize an empty list res.
  2. For each query q in queries:
    • set cur = -1 to represent "no covering interval found yet"
  3. Iterate through every interval [l, r]:
    • if l <= q <= r, then the interval covers q
    • compute its length len = r - l + 1
    • update cur if:
      • cur == -1 (first valid interval), or
      • len < cur (found a smaller covering interval)
  4. Append cur to res for this query.
  5. After all queries are processed, return res.
class Solution:
    def minInterval(self, intervals: List[List[int]], queries: List[int]) -> List[int]:
        n = len(intervals)
        res = []
        for q in queries:
            cur = -1
            for l, r in intervals:
                if l <= q <= r:
                    if cur == -1 or (r - l + 1) < cur:
                        cur = r - l + 1
            res.append(cur)
        return res

Time & Space Complexity

  • Time complexity: O(mn)O(m * n)
  • Space complexity:
    • O(1)O(1) extra space.
    • O(m)O(m) space for the output array.

Where mm is the length of the array queriesqueries and nn is the length of the array intervalsintervals.


2. Sweep Line Algorithm

Intuition

For each query value q, we want the length of the smallest interval [l, r] that contains q (l <= q <= r). If none exists, the answer is -1.

A sweep line approach processes everything (interval starts, interval ends, and queries) in sorted order of time.
While sweeping from left to right, we maintain a data structure of “currently active intervals” (intervals that have started but have not ended yet).
For a query time q, the answer is simply the smallest interval length among the active intervals.

To get that smallest length quickly, we use a min-heap ordered by interval size.

Because intervals can end later, we also need a way to remove expired intervals:

  • when an interval ends, we mark it as inactive
  • when answering a query, we pop inactive intervals from the heap until the top is active

Algorithm

  1. Build a list of events:
    • For each interval [start, end]:
      • Add a start event at start containing its length and index
      • Add an end event at end containing its index
    • For each query q:
      • Add a query event at q containing the query's original position
  2. Sort all events by (time, type) so they are processed in time order.
  3. Use:
    • a min-heap sizes storing (interval_length, interval_index) for active intervals
    • an array inactive to mark intervals that have ended
    • an output array ans initialized with -1
  4. Sweep through events in sorted order:
    • If it is an interval start:
      • push (length, idx) into the heap
    • If it is an interval end:
      • mark that interval index as inactive
    • If it is a query:
      • pop from the heap while the top interval is inactive
      • if heap is not empty, the top length is the smallest covering interval store it in ans
      • otherwise leave -1
  5. Return ans in the original query order.
class Solution:
    def minInterval(self, intervals: List[List[int]], queries: List[int]) -> List[int]:
        events = []
        # Create events for intervals
        for idx, (start, end) in enumerate(intervals):
            events.append((start, 0, end - start + 1, idx))
            events.append((end, 2, end - start + 1, idx))

        # Create events for queries
        for i, q in enumerate(queries):
            events.append((q, 1, i))

        # Sort by time and type (end before query)
        events.sort(key=lambda x: (x[0], x[1]))

        # Min heap storing [size, index]
        sizes = []
        ans = [-1] * len(queries)
        inactive = [False] * len(intervals)

        for time, type, *rest in events:
            if type == 0:  # Interval start
                interval_size, idx = rest
                heapq.heappush(sizes, (interval_size, idx))
            elif type == 2: #Interval end
                idx = rest[1]
                inactive[idx] = True
            else: # Query
                query_idx = rest[0]
                while sizes and inactive[sizes[0][1]]:
                    heapq.heappop(sizes)
                if sizes:
                    ans[query_idx] = sizes[0][0]

        return ans

Time & Space Complexity

  • Time complexity: O((n+m)log(n+m))O((n + m) \log (n + m))
  • Space complexity: O(n+m)O(n + m)

Where mm is the length of the array queriesqueries and nn is the length of the array intervalsintervals.


3. Min Heap

Intuition

For each query q, we want the length of the smallest interval [l, r] such that
l ≤ q ≤ r. If no interval covers q, the answer is -1.

A very efficient way to do this is:

  • process queries in sorted order
  • as queries increase, we add intervals whose start ≤ q
  • among those active intervals, remove any that end before q
  • the smallest valid interval is always at the top of a min heap

The heap is ordered by interval length, so the smallest covering interval is easy to find.

Algorithm

  1. Sort intervals by their start value.
  2. Sort the queries, but keep their original order for the final answer.
  3. Initialize:
    • a min heap minHeap storing (interval_length, interval_end)
    • an index i = 0 to iterate through intervals
    • a map res to store answers for each query
  4. For each query q in sorted order:
    • While there are intervals whose start ≤ q:
      • push (r - l + 1, r) into the heap
      • move i forward
    • While the heap is not empty and the top interval ends before q:
      • pop it from the heap
    • If the heap is not empty:
      • the top element's length is the answer for q
    • Otherwise:
      • the answer for q is -1
    • Store the result for q
  5. Return the answers in the original query order.
class Solution:
    def minInterval(self, intervals: List[List[int]], queries: List[int]) -> List[int]:
        intervals.sort()
        minHeap = []
        res = {}
        i = 0
        for q in sorted(queries):
            while i < len(intervals) and intervals[i][0] <= q:
                l, r = intervals[i]
                heapq.heappush(minHeap, (r - l + 1, r))
                i += 1

            while minHeap and minHeap[0][1] < q:
                heapq.heappop(minHeap)
            res[q] = minHeap[0][0] if minHeap else -1
        return [res[q] for q in queries]

Time & Space Complexity

  • Time complexity: O(nlogn+mlogm)O(n \log n + m \log m)
  • Space complexity: O(n+m)O(n + m)

Where mm is the length of the array queriesqueries and nn is the length of the array intervalsintervals.


4. Min Segment Tree (Lazy Propagation)

Intuition

For each query q, we need the length of the smallest interval [l, r] such that l <= q <= r.
If no interval covers q, the answer is -1.

This solution treats the number line as a set of discrete important points (all interval endpoints and all queries).
Then it uses a segment tree that supports:

  • Range update: “For every point covered by this interval, the best (minimum) interval length might become smaller.”
  • Point query: “At this query point, what is the minimum interval length that covers it?”

Since many intervals update large ranges, we use lazy propagation to apply updates efficiently without touching every point in the range.

Because coordinates can be large, we first do coordinate compression:

  • map every unique coordinate to a compact index 0..M-1
  • this allows the segment tree to work on a small index range

Algorithm

  1. Collect all important points
    • Add every interval start and end
    • Add every query value
  2. Coordinate compress
    • Sort and deduplicate points
    • Create a map compress[value] -> index
  3. Build a min segment tree with lazy propagation
    • Each node stores the minimum interval length applied to that segment
    • Lazy values store “pending minimum updates” that still need to be pushed down
  4. Apply each interval as a range update
    • For interval [l, r]:
      • convert to compressed indices [L, R]
      • compute its length len = r - l + 1
      • update the segment tree on range [L, R] with len using:
        • tree[node] = min(tree[node], len)
      • lazy propagation ensures this is efficient
  5. Answer each query with a point query
    • Convert query q to compressed index idx
    • Query the segment tree at idx to get the minimum covering length
    • If the value is still infinity, return -1 (no interval covers it)
  6. Return answers in the original query order.
class SegmentTree:
    def __init__(self, N):
        self.n = N
        self.tree = [float('inf')] * (4 * N)
        self.lazy = [float('inf')] * (4 * N)

    def propagate(self, treeidx, lo, hi):
        if self.lazy[treeidx] != float('inf'):
            self.tree[treeidx] = min(self.tree[treeidx], self.lazy[treeidx])
            if lo != hi:
                self.lazy[2 * treeidx + 1] = min(self.lazy[2 * treeidx + 1], self.lazy[treeidx])
                self.lazy[2 * treeidx + 2] = min(self.lazy[2 * treeidx + 2], self.lazy[treeidx])
            self.lazy[treeidx] = float('inf')

    def update(self, treeidx, lo, hi, left, right, val):
        self.propagate(treeidx, lo, hi)
        if lo > right or hi < left:
            return
        if lo >= left and hi <= right:
            self.lazy[treeidx] = min(self.lazy[treeidx], val)
            self.propagate(treeidx, lo, hi)
            return
        mid = (lo + hi) // 2
        self.update(2 * treeidx + 1, lo, mid, left, right, val)
        self.update(2 * treeidx + 2, mid + 1, hi, left, right, val)
        self.tree[treeidx] = min(self.tree[2 * treeidx + 1], self.tree[2 * treeidx + 2])

    def query(self, treeidx, lo, hi, idx):
        self.propagate(treeidx, lo, hi)
        if lo == hi:
            return self.tree[treeidx]
        mid = (lo + hi) // 2
        if idx <= mid:
            return self.query(2 * treeidx + 1, lo, mid, idx)
        else:
            return self.query(2 * treeidx + 2, mid + 1, hi, idx)

    def update_range(self, left, right, val):
        self.update(0, 0, self.n - 1, left, right, val)

    def query_point(self, idx):
        return self.query(0, 0, self.n - 1, idx)

class Solution:
    def minInterval(self, intervals: List[List[int]], queries: List[int]) -> List[int]:
        points = []
        for interval in intervals:
            points.append(interval[0])
            points.append(interval[1])
        for q in queries:
            points.append(q)

        # Compress the coordinates
        points = sorted(set(points))
        compress = {points[i]: i for i in range(len(points))}

        # Lazy Segment Tree
        segTree = SegmentTree(len(points))

        for interval in intervals:
            start = compress[interval[0]]
            end = compress[interval[1]]
            length = interval[1] - interval[0] + 1
            segTree.update_range(start, end, length)

        ans = []
        for q in queries:
            idx = compress[q]

            # query for minSize
            res = segTree.query_point(idx)
            ans.append(res if res != float('inf') else -1)
        return ans

Time & Space Complexity

  • Time complexity: O((n+m)logk)O((n + m)\log k)
  • Space complexity:
    • O(k)O(k) extra space.
    • O(m)O(m) space for the output array.

Where mm is the length of the array queriesqueries, nn is the length of the array intervalsintervals and kk is the number of unique points.


Common Pitfalls

Losing Original Query Order After Sorting

When sorting queries for efficient processing, the original indices must be preserved. Returning results in sorted query order instead of the original order produces incorrect output. Always pair each query with its original index before sorting.

Forgetting to Remove Expired Intervals from the Heap

Intervals that have ended (their right endpoint is less than the current query) must be removed from the heap before answering. Failing to pop expired intervals means the heap may return an interval that does not actually contain the query point.

Incorrect Event Ordering in Sweep Line Approach

When multiple events occur at the same coordinate, processing order matters. Interval end events should typically be processed before query events at the same point to ensure intervals ending exactly at the query point are still considered active.

Using Wrong Interval Length Calculation

The length of interval [l, r] is r - l + 1, not r - l. This off-by-one error affects which interval is considered smallest and leads to incorrect results when intervals differ by exactly one unit.

Not Handling Queries Outside All Intervals

When no interval covers a query, the answer must be -1. Forgetting to initialize the result array with -1 or not checking for an empty heap after removing expired intervals causes undefined or incorrect values to be returned.