Facebook Pixel

1847. Closest Room

Problem Description

You are given information about a hotel with n rooms, where each room has a unique room number and a size. The rooms are represented as a 2D array rooms where rooms[i] = [roomId_i, size_i] means there is a room with number roomId_i and size size_i.

You need to process k queries about room assignments. Each query is given as queries[j] = [preferred_j, minSize_j], where:

  • preferred_j is the preferred room number for this query
  • minSize_j is the minimum acceptable room size for this query

For each query, you need to find a room that:

  1. Has a size of at least minSize_j (the room must be large enough)
  2. Has a room number id that minimizes the absolute difference abs(id - preferred_j) (the room number should be as close as possible to the preferred room number)

If multiple rooms have the same absolute difference from the preferred room number (a tie), choose the room with the smaller room number. If no room meets the size requirement, return -1 for that query.

The output should be an array answer of length k, where answer[j] contains the room number assigned for the j-th query, or -1 if no suitable room exists.

For example, if the preferred room number is 5 and there are two rooms with numbers 3 and 7 that both meet the size requirement, both have an absolute difference of 2 from the preferred number. In this case, you would choose room 3 since it has the smaller room number.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight is that we can process queries in a specific order to optimize our search. Since each query has a minimum size requirement, if we process queries from smallest to largest minimum size, we can progressively eliminate rooms that are too small, rather than checking all rooms for each query.

Think about it this way: if we sort queries by their minSize in ascending order, once a room becomes too small for a query, it will remain too small for all subsequent queries (since they have larger minSize requirements). This means we can remove small rooms from consideration permanently as we process queries.

Here's the clever approach:

  1. Start by considering all rooms as available options
  2. Sort queries by their minSize (smallest first)
  3. For each query, remove all rooms that are now too small
  4. Among the remaining valid rooms, find the one closest to the preferred room number

To efficiently find the closest room number, we need a data structure that:

  • Allows us to remove elements (rooms that become too small)
  • Supports binary search to find the closest value to our preferred room number

This leads us to use an ordered set (like SortedList in Python). With binary search, we can find where the preferred room number would fit in the sorted list, then check the rooms immediately before and after that position to determine which is closest.

The reason we process queries "offline" (not in their original order) is that this sorting optimization significantly reduces repeated work. After processing all queries in this optimized order, we rearrange the answers back to match the original query order.

This transforms what could be an O(k * n) problem (checking all rooms for each query) into a more efficient solution where each room is considered for removal only once, and finding the closest room uses binary search O(log n).

Learn more about Binary Search and Sorting patterns.

Solution Approach

The solution implements the offline query approach with an ordered set and binary search. Let's walk through the implementation step by step:

1. Sort the rooms by size:

rooms.sort(key=lambda x: x[1])

We sort rooms in ascending order by their size. This allows us to efficiently remove rooms that are too small as we process queries.

2. Create an index array for queries sorted by minimum size:

k = len(queries)
idx = sorted(range(k), key=lambda i: queries[i][1])

Instead of sorting the queries directly (which would lose their original positions), we create an index array idx that tells us the order in which to process queries based on their minSize values.

3. Initialize data structures:

ans = [-1] * k
i, n = 0, len(rooms)
sl = SortedList(x[0] for x in rooms)
  • ans: Result array initialized with -1 (default when no room is found)
  • i: Pointer to track which rooms have been removed for being too small
  • sl: SortedList containing all room IDs initially, maintaining sorted order for binary search

4. Process each query in ascending order of minimum size:

for j in idx:
    prefer, minSize = queries[j]

We process queries using the sorted index array, getting each query's preferred room and minimum size.

5. Remove rooms that are too small:

while i < n and rooms[i][1] < minSize:
    sl.remove(rooms[i][0])
    i += 1

Since rooms are sorted by size, we remove all rooms with size less than minSize from the SortedList. These rooms won't be valid for any future queries either (since future queries have larger minSize).

6. Check if any rooms remain:

if i == n:
    break

If all rooms have been removed, all remaining queries will have answer -1.

7. Find the closest room using binary search:

p = sl.bisect_left(prefer)

bisect_left finds the position where prefer would be inserted in the sorted list. This gives us the index of the smallest room ID that is greater than or equal to prefer.

8. Check candidates and select the closest:

if p < len(sl):
    ans[j] = sl[p]
if p and (ans[j] == -1 or ans[j] - prefer >= prefer - sl[p - 1]):
    ans[j] = sl[p - 1]
  • First, if there's a room at position p (room ID ≥ prefer), it's a candidate
  • Then, if there's a room at position p-1 (room ID < prefer), we check if it's closer or equally close to the preferred room
  • When distances are equal, we choose the smaller room ID (which is sl[p-1] since it comes before sl[p])

The condition ans[j] - prefer >= prefer - sl[p - 1] checks if the room at p-1 is closer or equally close. If equal, we prefer the smaller room ID.

Time Complexity: O(n log n + k log k + k log n) where sorting rooms takes O(n log n), sorting query indices takes O(k log k), and for each query, binary search and removal operations take O(log n).

Space Complexity: O(n + k) for the SortedList and answer array.

Ready to land your dream job?

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

Start Evaluator

Example Walkthrough

Let's walk through a concrete example to illustrate the solution approach.

Given:

  • Rooms: [[1, 4], [2, 3], [3, 5], [4, 1]]
  • Queries: [[2, 3], [3, 4], [5, 2]]

Step 1: Sort rooms by size After sorting: [[4, 1], [2, 3], [1, 4], [3, 5]]

  • Room 4 has size 1
  • Room 2 has size 3
  • Room 1 has size 4
  • Room 3 has size 5

Step 2: Create sorted query indices Query minimum sizes: [3, 4, 2] Sorted indices: [2, 0, 1] (process query 2 first with minSize=2, then query 0 with minSize=3, then query 1 with minSize=4)

Step 3: Initialize SortedList with all room IDs sl = [1, 2, 3, 4] (sorted room numbers)

Step 4: Process queries in sorted order

Processing Query 2 (preferred=5, minSize=2):

  • Remove rooms with size < 2: Remove room 4 (size 1)
  • sl = [1, 2, 3]
  • Find closest to 5: bisect_left(5) = 3 (would insert at end)
  • Check candidate at p-1=2: room 3 has distance |3-5|=2
  • Answer for query 2: room 3

Processing Query 0 (preferred=2, minSize=3):

  • Remove rooms with size < 3: No rooms to remove (room 2 has size 3)
  • sl = [1, 2, 3]
  • Find closest to 2: bisect_left(2) = 1 (position of room 2)
  • Check candidate at p=1: room 2 has distance |2-2|=0
  • Check candidate at p-1=0: room 1 has distance |1-2|=1
  • Room 2 is closer (distance 0 vs 1)
  • Answer for query 0: room 2

Processing Query 1 (preferred=3, minSize=4):

  • Remove rooms with size < 4: Remove room 2 (size 3)
  • sl = [1, 3]
  • Find closest to 3: bisect_left(3) = 1 (position of room 3)
  • Check candidate at p=1: room 3 has distance |3-3|=0
  • Check candidate at p-1=0: room 1 has distance |1-3|=2
  • Room 3 is closer (distance 0 vs 2)
  • Answer for query 1: room 3

Step 5: Arrange answers in original query order

  • Query 0 → room 2
  • Query 1 → room 3
  • Query 2 → room 3

Final Answer: [2, 3, 3]

This example demonstrates how:

  • Processing queries by ascending minSize allows us to progressively remove small rooms
  • Binary search efficiently finds the closest room among valid options
  • The offline approach avoids redundant work by removing each room at most once

Solution Implementation

1from typing import List
2from sortedcontainers import SortedList
3
4
5class Solution:
6    def closestRoom(
7        self, rooms: List[List[int]], queries: List[List[int]]
8    ) -> List[int]:
9        """
10        Find the closest room ID to preferred ID that has size >= minSize for each query.
11      
12        Args:
13            rooms: List of [roomId, size] pairs
14            queries: List of [preferred, minSize] pairs
15          
16        Returns:
17            List of closest valid room IDs for each query (-1 if no valid room exists)
18        """
19        # Sort rooms by size in ascending order
20        rooms.sort(key=lambda room: room[1])
21      
22        # Get number of queries
23        num_queries = len(queries)
24      
25        # Create indices sorted by query's minSize in ascending order
26        sorted_query_indices = sorted(range(num_queries), key=lambda i: queries[i][1])
27      
28        # Initialize result array with -1 (no valid room found)
29        result = [-1] * num_queries
30      
31        # Initialize room pointer and total number of rooms
32        room_index = 0
33        num_rooms = len(rooms)
34      
35        # Create sorted list of all room IDs initially
36        valid_room_ids = SortedList(room[0] for room in rooms)
37      
38        # Process queries in order of increasing minSize
39        for query_idx in sorted_query_indices:
40            preferred_id, min_size = queries[query_idx]
41          
42            # Remove rooms that are too small for current query
43            while room_index < num_rooms and rooms[room_index][1] < min_size:
44                valid_room_ids.remove(rooms[room_index][0])
45                room_index += 1
46          
47            # If all rooms are too small, remaining queries won't find valid rooms
48            if room_index == num_rooms:
49                break
50          
51            # Find position where preferred_id would be inserted
52            insertion_pos = valid_room_ids.bisect_left(preferred_id)
53          
54            # Check room at or after preferred_id position
55            if insertion_pos < len(valid_room_ids):
56                result[query_idx] = valid_room_ids[insertion_pos]
57          
58            # Check room before preferred_id position and choose closer one
59            if insertion_pos > 0:
60                left_room_id = valid_room_ids[insertion_pos - 1]
61                # If no room found yet or left room is closer/equally close
62                if result[query_idx] == -1 or result[query_idx] - preferred_id >= preferred_id - left_room_id:
63                    result[query_idx] = left_room_id
64      
65        return result
66
1class Solution {
2    public int[] closestRoom(int[][] rooms, int[][] queries) {
3        int numRooms = rooms.length;
4        int numQueries = queries.length;
5      
6        // Sort rooms by size in ascending order
7        Arrays.sort(rooms, (a, b) -> a[1] - b[1]);
8      
9        // Create an array of query indices for processing queries in sorted order
10        Integer[] queryIndices = new Integer[numQueries];
11        for (int i = 0; i < numQueries; i++) {
12            queryIndices[i] = i;
13        }
14      
15        // Sort query indices by minimum size requirement in ascending order
16        Arrays.sort(queryIndices, (i, j) -> queries[i][1] - queries[j][1]);
17      
18        // TreeMap to maintain sorted room IDs that meet size requirements
19        // Key: room ID, Value: count of rooms with that ID
20        TreeMap<Integer, Integer> availableRooms = new TreeMap<>();
21      
22        // Initially, add all rooms to the TreeMap
23        for (int[] room : rooms) {
24            availableRooms.merge(room[0], 1, Integer::sum);
25        }
26      
27        // Initialize result array with -1 (no room found)
28        int[] result = new int[numQueries];
29        Arrays.fill(result, -1);
30      
31        // Track the index of the smallest room not yet removed
32        int roomIndex = 0;
33      
34        // Process queries in order of increasing minimum size requirement
35        for (int queryIdx : queryIndices) {
36            int preferredRoomId = queries[queryIdx][0];
37            int minSizeRequired = queries[queryIdx][1];
38          
39            // Remove rooms that are too small for current query
40            while (roomIndex < numRooms && rooms[roomIndex][1] < minSizeRequired) {
41                int roomIdToRemove = rooms[roomIndex][0];
42                // Decrease count and remove if count becomes 0
43                if (availableRooms.merge(roomIdToRemove, -1, Integer::sum) == 0) {
44                    availableRooms.remove(roomIdToRemove);
45                }
46                roomIndex++;
47            }
48          
49            // If all rooms have been removed, no valid rooms for remaining queries
50            if (roomIndex == numRooms) {
51                break;
52            }
53          
54            // Find the closest room ID >= preferred ID
55            Integer ceilingRoomId = availableRooms.ceilingKey(preferredRoomId);
56            if (ceilingRoomId != null) {
57                result[queryIdx] = ceilingRoomId;
58            }
59          
60            // Find the closest room ID <= preferred ID
61            Integer floorRoomId = availableRooms.floorKey(preferredRoomId);
62            if (floorRoomId != null) {
63                // Update result if floor room is closer or equally close (prefer smaller ID)
64                if (result[queryIdx] == -1 || 
65                    result[queryIdx] - preferredRoomId >= preferredRoomId - floorRoomId) {
66                    result[queryIdx] = floorRoomId;
67                }
68            }
69        }
70      
71        return result;
72    }
73}
74
1class Solution {
2public:
3    vector<int> closestRoom(vector<vector<int>>& rooms, vector<vector<int>>& queries) {
4        int numRooms = rooms.size();
5        int numQueries = queries.size();
6      
7        // Sort rooms by size in ascending order
8        sort(rooms.begin(), rooms.end(), [](const vector<int>& a, const vector<int>& b) {
9            return a[1] < b[1];
10        });
11      
12        // Create indices array to track original query positions
13        vector<int> queryIndices(numQueries);
14        iota(queryIndices.begin(), queryIndices.end(), 0);
15      
16        // Sort query indices by minimum size requirement in ascending order
17        sort(queryIndices.begin(), queryIndices.end(), [&](int i, int j) {
18            return queries[i][1] < queries[j][1];
19        });
20      
21        // Initialize result array with -1 (no valid room found)
22        vector<int> result(numQueries, -1);
23      
24        // Pointer to track rooms that don't meet size requirement
25        int roomPointer = 0;
26      
27        // Multiset to store valid room IDs for binary search
28        multiset<int> validRoomIds;
29      
30        // Initially add all room IDs to the set
31        for (const auto& room : rooms) {
32            validRoomIds.insert(room[0]);
33        }
34      
35        // Process queries in order of increasing size requirement
36        for (int queryIdx : queryIndices) {
37            int preferredId = queries[queryIdx][0];
38            int minSizeRequired = queries[queryIdx][1];
39          
40            // Remove rooms that are too small for current query
41            while (roomPointer < numRooms && rooms[roomPointer][1] < minSizeRequired) {
42                validRoomIds.erase(validRoomIds.find(rooms[roomPointer][0]));
43                roomPointer++;
44            }
45          
46            // If all rooms are too small, remaining queries will have no valid rooms
47            if (roomPointer == numRooms) {
48                break;
49            }
50          
51            // Find the closest room ID to the preferred ID
52            // First, check room with ID >= preferredId
53            auto upperBoundIt = validRoomIds.lower_bound(preferredId);
54            if (upperBoundIt != validRoomIds.end()) {
55                result[queryIdx] = *upperBoundIt;
56            }
57          
58            // Then, check room with ID < preferredId
59            if (upperBoundIt != validRoomIds.begin()) {
60                --upperBoundIt;
61                // Update result if this room is closer or equally close (prefer smaller ID)
62                if (result[queryIdx] == -1 || 
63                    abs(*upperBoundIt - preferredId) <= abs(result[queryIdx] - preferredId)) {
64                    result[queryIdx] = *upperBoundIt;
65                }
66            }
67        }
68      
69        return result;
70    }
71};
72
1function closestRoom(rooms: number[][], queries: number[][]): number[] {
2    const numRooms = rooms.length;
3    const numQueries = queries.length;
4  
5    // Sort rooms by size in ascending order
6    rooms.sort((a, b) => a[1] - b[1]);
7  
8    // Create indices array to track original query positions
9    const queryIndices: number[] = Array.from({ length: numQueries }, (_, i) => i);
10  
11    // Sort query indices by minimum size requirement in ascending order
12    queryIndices.sort((i, j) => queries[i][1] - queries[j][1]);
13  
14    // Initialize result array with -1 (no valid room found)
15    const result: number[] = new Array(numQueries).fill(-1);
16  
17    // Pointer to track rooms that don't meet size requirement
18    let roomPointer = 0;
19  
20    // Set to store valid room IDs for binary search operations
21    const validRoomIds = new Set<number>();
22  
23    // Initially add all room IDs to the set
24    for (const room of rooms) {
25        validRoomIds.add(room[0]);
26    }
27  
28    // Process queries in order of increasing size requirement
29    for (const queryIdx of queryIndices) {
30        const preferredId = queries[queryIdx][0];
31        const minSizeRequired = queries[queryIdx][1];
32      
33        // Remove rooms that are too small for current query
34        while (roomPointer < numRooms && rooms[roomPointer][1] < minSizeRequired) {
35            validRoomIds.delete(rooms[roomPointer][0]);
36            roomPointer++;
37        }
38      
39        // If all rooms are too small, remaining queries will have no valid rooms
40        if (roomPointer === numRooms) {
41            break;
42        }
43      
44        // Convert set to sorted array for binary search
45        const sortedValidIds = Array.from(validRoomIds).sort((a, b) => a - b);
46      
47        // Find the closest room ID to the preferred ID
48        let closestId = -1;
49        let minDistance = Infinity;
50      
51        // Binary search to find the position where preferredId would be inserted
52        let left = 0;
53        let right = sortedValidIds.length - 1;
54        let insertPos = sortedValidIds.length;
55      
56        while (left <= right) {
57            const mid = Math.floor((left + right) / 2);
58            if (sortedValidIds[mid] >= preferredId) {
59                insertPos = mid;
60                right = mid - 1;
61            } else {
62                left = mid + 1;
63            }
64        }
65      
66        // Check room with ID >= preferredId (at insertPos)
67        if (insertPos < sortedValidIds.length) {
68            const distance = Math.abs(sortedValidIds[insertPos] - preferredId);
69            if (distance < minDistance) {
70                minDistance = distance;
71                closestId = sortedValidIds[insertPos];
72            }
73        }
74      
75        // Check room with ID < preferredId (at insertPos - 1)
76        if (insertPos > 0) {
77            const distance = Math.abs(sortedValidIds[insertPos - 1] - preferredId);
78            // Update if this room is closer or equally close (prefer smaller ID in case of tie)
79            if (distance <= minDistance) {
80                minDistance = distance;
81                closestId = sortedValidIds[insertPos - 1];
82            }
83        }
84      
85        result[queryIdx] = closestId;
86    }
87  
88    return result;
89}
90

Time and Space Complexity

Time Complexity: O(n × log n + k × log k + n × log n + k × log n)

Breaking down the operations:

  • Sorting rooms by size: O(n × log n)
  • Sorting query indices by minSize: O(k × log k)
  • Initial SortedList construction: O(n × log n) (inserting n elements, each taking O(log n))
  • For each query processing:
    • Removing rooms from SortedList: Each removal takes O(log n), and we remove at most n rooms total across all queries, giving O(n × log n)
    • Binary search (bisect_left) on SortedList: O(log n) per query, totaling O(k × log n)
    • Accessing elements and comparisons: O(1) per query

The dominant terms simplify to: O(n × log n + k × log k + k × log n)

However, the reference answer gives O(n × log n + k × log k), which appears to be overlooking the cost of SortedList operations. The actual complexity includes the additional O(n × log n) for SortedList construction and O(k × log n) for binary searches.

Space Complexity: O(n + k)

Breaking down the space usage:

  • Sorted rooms list: O(n) (in-place sort)
  • Query indices array: O(k)
  • Answer array: O(k)
  • SortedList: O(n) (stores at most n room IDs)

Total space complexity is O(n + k), which matches the reference answer.

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

Common Pitfalls

1. Incorrect Tie-Breaking When Distances Are Equal

One of the most common mistakes is incorrectly handling the tie-breaking rule when two rooms have the same absolute difference from the preferred room number.

Pitfall Example:

# INCORRECT: Using > instead of >=
if insertion_pos > 0:
    left_room_id = valid_room_ids[insertion_pos - 1]
    if result[query_idx] == -1 or result[query_idx] - preferred_id > preferred_id - left_room_id:
        result[query_idx] = left_room_id

When the distances are equal, the problem requires choosing the room with the smaller room number. Using > would keep the larger room number when distances are equal, violating the requirement.

Correct Solution:

# CORRECT: Using >= to prefer smaller room ID when distances are equal
if insertion_pos > 0:
    left_room_id = valid_room_ids[insertion_pos - 1]
    if result[query_idx] == -1 or result[query_idx] - preferred_id >= preferred_id - left_room_id:
        result[query_idx] = left_room_id

2. Processing Queries in Original Order Instead of Sorted Order

Pitfall Example:

# INCORRECT: Processing queries in original order
for j in range(len(queries)):
    prefer, minSize = queries[j]
    # This would require re-adding rooms for smaller minSize queries

Processing queries in their original order would be inefficient and complex since you'd need to handle both adding and removing rooms from the valid set.

Correct Solution: Process queries sorted by minSize and use an index array to maintain the mapping to original positions:

sorted_query_indices = sorted(range(num_queries), key=lambda i: queries[i][1])
for query_idx in sorted_query_indices:
    # Process query while maintaining original position mapping

3. Not Handling Edge Cases with Binary Search

Pitfall Example:

# INCORRECT: Assuming bisect_left always returns valid positions
p = valid_room_ids.bisect_left(preferred_id)
result[query_idx] = valid_room_ids[p]  # May cause IndexError
result[query_idx] = valid_room_ids[p - 1]  # May access negative index

Correct Solution: Always check bounds before accessing elements:

# Check if position is within bounds
if insertion_pos < len(valid_room_ids):
    result[query_idx] = valid_room_ids[insertion_pos]

# Check if there's a valid previous element
if insertion_pos > 0:
    left_room_id = valid_room_ids[insertion_pos - 1]

4. Modifying the Original Data Structure While Iterating

Pitfall Example:

# INCORRECT: Modifying rooms list while using it
rooms.sort(key=lambda x: x[1])
for query in queries:
    # Remove small rooms from original rooms list
    rooms = [r for r in rooms if r[1] >= query[1]]  # Inefficient and error-prone

Correct Solution: Use a separate data structure (SortedList) and maintain an index pointer:

valid_room_ids = SortedList(room[0] for room in rooms)
room_index = 0
while room_index < num_rooms and rooms[room_index][1] < min_size:
    valid_room_ids.remove(rooms[room_index][0])
    room_index += 1

5. Breaking Too Early When No Valid Rooms Exist

Pitfall Example:

# INCORRECT: Breaking immediately when no valid rooms for current query
if len(valid_room_ids) == 0:
    break  # This might skip queries that should return -1

Correct Solution: Only break when all rooms have been processed, ensuring remaining queries get -1:

if room_index == num_rooms:
    break  # All rooms processed, remaining queries stay as -1
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Consider the classic dynamic programming of fibonacci numbers, what is the recurrence relation?


Recommended Readings

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

Load More