Facebook Pixel

3275. K-th Nearest Obstacle Queries


Problem Description

You are given an infinite 2D plane with no obstacles initially. Given a positive integer k and a list of queries queries, you will build obstacles sequentially at specific coordinates. Each query will provide coordinates [x, y] for an obstacle, and no coordinate will be repeated. For each query, you need to determine the distance to the kth closest obstacle from the origin (0, 0). If there are fewer than k obstacles after a query, return -1 for that query. The distance of an obstacle from the origin is calculated as |x| + |y|.

Intuition

The problem requires tracking distances of obstacles from the origin and maintaining an ordered list of these distances to efficiently find the kth smallest value after each insertion. For this, a max-heap is suitable since it allows us to keep the smallest k distances while discarding larger ones.

Here's the step-by-step breakdown of the solution:

  1. Initialize a Max-Heap: Use a max-heap to store obstacle distances in a way that always leaves the largest of the k smallest distances at the top. This allows easy replacement as new obstacles are added.

  2. Iterate Over Queries: For each query in queries, calculate the Manhattan distance of the obstacle (x, y) from the origin.

  3. Manage Heap Size: Add the calculated distance to the max-heap. If the size of the heap exceeds k, remove the top element, which is the largest among the k+1 smallest distances.

  4. Construct the Result: After processing each query, if the heap has at least k elements, the obstacle distance at the top represents the kth closest distance from the origin. Otherwise, append -1 to the result list indicating insufficient obstacles.

By following this process for each query, the algorithm efficiently determines and manages the kth nearest distance dynamically.

Learn more about Heap (Priority Queue) patterns.

Solution Approach

The solution employs a priority queue, specifically a max-heap, to efficiently track and retrieve the kth nearest obstacle distance after each query.

Implementation Steps:

  1. Initialize a Max-Heap: Use Python's heapq library which by default provides a min-heap. To simulate a max-heap, store negative distances.

  2. Iterate Through Queries:

    • For each obstacle coordinate (x, y) in queries, calculate its distance from the origin using the formula distance = |x| + |y|.
    • Push this distance as a negative value into the heap using heappush.
  3. Maintain Heap Size:

    • If the heap size exceeds k, pop the largest element using heappop to ensure only the k smallest distances remain actively managed.
    • For efficiency, ensure only k elements are stored in the heap at any point after processing a sufficient number of queries.
  4. Build the Result Array:

    • For each query processed, if the number of obstacles (i.e., the size of the heap) is at least k, append the top of the heap to the result list (flipping the sign back to positive).
    • If fewer than k obstacles are present, append -1.

By leveraging the max-heap, this approach efficiently handles updates with each query and dynamically retrieves the kth closest distance without needing to sort all obstacle distances repeatedly. The complexity primarily revolves around heap operations, making this method both effective and scalable for large sets of queries.

Ready to land your dream job?

Unlock your dream job with a 2-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's consider an example to understand the solution approach for determining the kth closest obstacle distance.

Suppose we have k = 2 and queries = [[3, 2], [1, 4], [0, 6], [2, 3]].

To solve this, we will use a max-heap to keep track of the smallest k distances.

  1. Initialize the Max-Heap: Start with an empty heap.

  2. Process Each Query:

    • Query 1: Obstacle at [3, 2]

      • Calculate the Manhattan distance: |3| + |2| = 5.
      • Add -5 to the heap to simulate a max-heap.
      • Heap status: [-5]
      • Since we have fewer than k distances, append -1 to the result.
    • Query 2: Obstacle at [1, 4]

      • Calculate the Manhattan distance: |1| + |4| = 5.
      • Add -5 to the heap.
      • Heap status: [-5, -5]
      • We now have k distances, so append 5 (flip sign) to the result.
    • Query 3: Obstacle at [0, 6]

      • Calculate the Manhattan distance: |0| + |6| = 6.
      • Add -6 to the heap.
      • Heap status: [-6, -5, -5]
      • Since the heap exceeds k, remove the largest: -6.
      • Remaining heap: [-5, -5]
      • Append 5 to the result.
    • Query 4: Obstacle at [2, 3]

      • Calculate the Manhattan distance: |2| + |3| = 5.
      • Add -5 to the heap.
      • Heap status: [-5, -5, -5]
      • Remove the largest: -5.
      • Remaining heap: [-5, -5]
      • Append 5 to the result.

Final Result: [-1, 5, 5, 5]

This example demonstrates how using a max-heap helps efficiently manage the nearest distances, illuminating the kth nearest obstacle distance after each query.

Solution Implementation

1from typing import List
2from heapq import heappush, heappop
3
4class Solution:
5    def resultsArray(self, queries: List[List[int]], k: int) -> List[int]:
6        # Initialize an empty list to store the results
7        results = []
8        # Initialize a priority queue (min-heap) to store the negative of distances
9        priority_queue = []
10      
11        # Iterate over each query with its index
12        for index, (x, y) in enumerate(queries):
13            # Push the negative of the Manhattan distance into the heap
14            heappush(priority_queue, -(abs(x) + abs(y)))
15          
16            # If there are more than k elements, pop the smallest one (the largest distance)
17            if index >= k:
18                heappop(priority_queue)
19          
20            # If the condition i >= k - 1 is met, append the maximum distance seen so far
21            # Otherwise, append -1 as the result
22            max_distance = -priority_queue[0] if index >= k - 1 else -1
23            results.append(max_distance)
24      
25        # Return the list of results
26        return results
27
1import java.util.PriorityQueue;
2import java.util.Collections;
3
4class Solution {
5    public int[] resultsArray(int[][] queries, int k) {
6        int n = queries.length; // Get the number of queries
7        int[] ans = new int[n]; // Initialize the results array with length n
8        // Initialize a max-heap priority queue using reverse order comparator
9        PriorityQueue<Integer> maxHeap = new PriorityQueue<>(Collections.reverseOrder());
10
11        // Iterate through each query
12        for (int i = 0; i < n; ++i) {
13            // Calculate the Manhattan distance of the current query point
14            int manhattanDistance = Math.abs(queries[i][0]) + Math.abs(queries[i][1]);
15            // Add the calculated distance to the max-heap
16            maxHeap.offer(manhattanDistance);
17          
18            // If more than 'k' elements are in the heap, remove the largest one
19            if (i >= k) {
20                maxHeap.poll();
21            }
22
23            // For indices greater than or equal to k-1, store the current maximum in the array
24            // Otherwise, store -1 since k elements have not yet been added
25            ans[i] = i >= k - 1 ? maxHeap.peek() : -1;
26        }
27      
28        return ans; // Return the results array
29    }
30}
31
1class Solution {
2public:
3    // This function processes a series of queries and maintains a min-heap of size k to track results.
4    vector<int> resultsArray(vector<vector<int>>& queries, int k) {
5        vector<int> ans; // Initialize a vector to store the results.
6        priority_queue<int> pq; // Declare a max-heap (priority queue).
7
8        // Iterate over each query in the list of queries.
9        for (const auto& query : queries) {
10            // Calculate the Manhattan distance as sum of absolute values of x and y.
11            int manhattanDistance = abs(query[0]) + abs(query[1]);
12            // Push the calculated distance into the priority queue.
13            pq.push(manhattanDistance);
14
15            // Ensure the size of the heap does not exceed k.
16            if (pq.size() > k) {
17                pq.pop(); // Pop the largest element to maintain the size of k.
18            }
19
20            // If the heap size equals k, record the top element, else record -1.
21            ans.push_back(pq.size() == k ? pq.top() : -1);
22        }
23
24        return ans; // Return the results vector.
25    }
26};
27
1// Import the MaxPriorityQueue class from the data structures library
2import { MaxPriorityQueue } from '@datastructures-js/priority-queue';
3
4/**
5 * Function to process an array of queries and return the results array.
6 * @param queries - A list of queries where each query is a tuple [x, y].
7 * @param k - The number of elements to keep in the priority queue at maximum.
8 * @returns An array of numbers representing the maximum k-th Manhattan distances at each step.
9 */
10function resultsArray(queries: number[][], k: number): number[] {
11    // Initialize a max-priority queue to store Manhattan distances.
12    const priorityQueue = new MaxPriorityQueue();
13    // Array to store the results.
14    const results: number[] = [];
15
16    // Iterate through each query in the array of queries.
17    for (const [x, y] of queries) {
18        // Calculate the Manhattan distance for the given coordinates.
19        const manhattanDistance = Math.abs(x) + Math.abs(y);
20        // Enqueue the Manhattan distance into the priority queue.
21        priorityQueue.enqueue(manhattanDistance);
22      
23        // If the size of the priority queue exceeds k, dequeue the maximum element.
24        if (priorityQueue.size() > k) {
25            priorityQueue.dequeue();
26        }
27      
28        // If the size of the priority queue is exactly k, push the maximum element onto results; otherwise, push -1.
29        results.push(priorityQueue.size() === k ? priorityQueue.front().element : -1);
30    }
31
32    // Return the computed results array.
33    return results;
34}
35

Time and Space Complexity

The time complexity is O(n \times \log k) because for each query in queries, the procedure heappush and potentially heappop operations are involved. Each of these heap operations takes O(\log k) time since the heap size is maintained at most k. This results in processing n queries leading to a total time complexity of O(n \times \log k).

The space complexity is O(k) since at any point in time, the priority queue pq holds no more than k elements. This is ensured by removing the smallest (in the context of max heap, most negative) element whenever the size of pq surpasses k. Therefore, the maximum space usage by the heap is O(k).

Learn more about how to find time and space complexity quickly.


Discover Your Strengths and Weaknesses: Take Our 2-Minute Quiz to Tailor Your Study Plan:
Question 1 out of 10

Which of these pictures shows the visit order of a depth-first search?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!


Load More