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 queryminSize_j
is the minimum acceptable room size for this query
For each query, you need to find a room that:
- Has a size of at least
minSize_j
(the room must be large enough) - Has a room number
id
that minimizes the absolute differenceabs(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.
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:
- Start by considering all rooms as available options
- Sort queries by their
minSize
(smallest first) - For each query, remove all rooms that are now too small
- 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 smallsl
: 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 beforesl[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 EvaluatorExample 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 takingO(log n)
) - For each query processing:
- Removing rooms from SortedList: Each removal takes
O(log n)
, and we remove at mostn
rooms total across all queries, givingO(n × log n)
- Binary search (bisect_left) on SortedList:
O(log n)
per query, totalingO(k × log n)
- Accessing elements and comparisons:
O(1)
per query
- Removing rooms from SortedList: Each removal takes
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
Consider the classic dynamic programming of fibonacci numbers, what is the recurrence relation?
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!