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_jis the preferred room number for this queryminSize_jis 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
idthat 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 an offline query approach with binary search template to find the closest room ID.
1. Sort and prepare data:
- Sort rooms by size in ascending order
- Create sorted query indices by minimum size requirement
2. Binary Search Template for Finding Closest Room:
We use the template to find the first room ID >= preferred ID:
left, right = 0, n - 1 first_true_index = -1 while left <= right: mid = (left + right) // 2 if ids[mid] >= preferred_id: first_true_index = mid right = mid - 1 else: left = mid + 1
The feasibility condition ids[mid] >= preferred_id creates the pattern:
- Room IDs smaller than preferred: return
false - Room IDs at or after preferred: return
true
3. Handle the result:
# Check room at or after preferred_id if first_true_index != -1: result[query_idx] = ids[first_true_index] # Check room before preferred_id and choose closer one if first_true_index == -1: # All rooms < preferred_id, closest is the last one result[query_idx] = ids[n - 1] elif first_true_index > 0: left_room_id = ids[first_true_index - 1] if result[query_idx] - preferred_id >= preferred_id - left_room_id: result[query_idx] = left_room_id
When first_true_index == -1, all room IDs are less than preferred, so the closest is the largest (last) room ID.
Time Complexity: O(n log n + k log k + k log n)
Space Complexity: O(n + k)
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 rooms.sort(key=lambda room: room[1])
10 num_queries = len(queries)
11 sorted_query_indices = sorted(range(num_queries), key=lambda i: queries[i][1])
12 result = [-1] * num_queries
13 room_index = 0
14 num_rooms = len(rooms)
15 valid_room_ids = SortedList(room[0] for room in rooms)
16
17 def find_first_ge(target: int) -> int:
18 """Find first room ID >= target using binary search template."""
19 ids = list(valid_room_ids)
20 left, right = 0, len(ids) - 1
21 first_true_index = -1
22
23 while left <= right:
24 mid = (left + right) // 2
25 if ids[mid] >= target:
26 first_true_index = mid
27 right = mid - 1
28 else:
29 left = mid + 1
30
31 return first_true_index
32
33 for query_idx in sorted_query_indices:
34 preferred_id, min_size = queries[query_idx]
35
36 while room_index < num_rooms and rooms[room_index][1] < min_size:
37 valid_room_ids.remove(rooms[room_index][0])
38 room_index += 1
39
40 if room_index == num_rooms:
41 break
42
43 ids = list(valid_room_ids)
44 n = len(ids)
45
46 # Find first room ID >= preferred_id
47 left, right = 0, n - 1
48 first_true_index = -1
49
50 while left <= right:
51 mid = (left + right) // 2
52 if ids[mid] >= preferred_id:
53 first_true_index = mid
54 right = mid - 1
55 else:
56 left = mid + 1
57
58 # Check room at or after preferred_id
59 if first_true_index != -1:
60 result[query_idx] = ids[first_true_index]
61
62 # Check room before preferred_id and choose closer one
63 if first_true_index == -1:
64 # All rooms < preferred_id, closest is the last one
65 result[query_idx] = ids[n - 1]
66 elif first_true_index > 0:
67 left_room_id = ids[first_true_index - 1]
68 if result[query_idx] - preferred_id >= preferred_id - left_room_id:
69 result[query_idx] = left_room_id
70
71 return result
721class Solution {
2 public int[] closestRoom(int[][] rooms, int[][] queries) {
3 int numRooms = rooms.length;
4 int numQueries = queries.length;
5
6 Arrays.sort(rooms, (a, b) -> a[1] - b[1]);
7
8 Integer[] queryIndices = new Integer[numQueries];
9 for (int i = 0; i < numQueries; i++) {
10 queryIndices[i] = i;
11 }
12 Arrays.sort(queryIndices, (i, j) -> queries[i][1] - queries[j][1]);
13
14 TreeMap<Integer, Integer> availableRooms = new TreeMap<>();
15 for (int[] room : rooms) {
16 availableRooms.merge(room[0], 1, Integer::sum);
17 }
18
19 int[] result = new int[numQueries];
20 Arrays.fill(result, -1);
21 int roomIndex = 0;
22
23 for (int queryIdx : queryIndices) {
24 int preferredRoomId = queries[queryIdx][0];
25 int minSizeRequired = queries[queryIdx][1];
26
27 while (roomIndex < numRooms && rooms[roomIndex][1] < minSizeRequired) {
28 int roomIdToRemove = rooms[roomIndex][0];
29 if (availableRooms.merge(roomIdToRemove, -1, Integer::sum) == 0) {
30 availableRooms.remove(roomIdToRemove);
31 }
32 roomIndex++;
33 }
34
35 if (roomIndex == numRooms) {
36 break;
37 }
38
39 // Use binary search template to find first room ID >= preferredRoomId
40 Integer[] ids = availableRooms.keySet().toArray(new Integer[0]);
41 int n = ids.length;
42 int left = 0, right = n - 1;
43 int firstTrueIndex = -1;
44
45 while (left <= right) {
46 int mid = left + (right - left) / 2;
47 if (ids[mid] >= preferredRoomId) {
48 firstTrueIndex = mid;
49 right = mid - 1;
50 } else {
51 left = mid + 1;
52 }
53 }
54
55 if (firstTrueIndex != -1) {
56 result[queryIdx] = ids[firstTrueIndex];
57 }
58
59 if (firstTrueIndex == -1) {
60 result[queryIdx] = ids[n - 1];
61 } else if (firstTrueIndex > 0) {
62 int leftRoomId = ids[firstTrueIndex - 1];
63 if (result[queryIdx] - preferredRoomId >= preferredRoomId - leftRoomId) {
64 result[queryIdx] = leftRoomId;
65 }
66 }
67 }
68
69 return result;
70 }
71}
721class 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.begin(), rooms.end(), [](const vector<int>& a, const vector<int>& b) {
8 return a[1] < b[1];
9 });
10
11 vector<int> queryIndices(numQueries);
12 iota(queryIndices.begin(), queryIndices.end(), 0);
13 sort(queryIndices.begin(), queryIndices.end(), [&](int i, int j) {
14 return queries[i][1] < queries[j][1];
15 });
16
17 vector<int> result(numQueries, -1);
18 int roomPointer = 0;
19 multiset<int> validRoomIds;
20
21 for (const auto& room : rooms) {
22 validRoomIds.insert(room[0]);
23 }
24
25 for (int queryIdx : queryIndices) {
26 int preferredId = queries[queryIdx][0];
27 int minSizeRequired = queries[queryIdx][1];
28
29 while (roomPointer < numRooms && rooms[roomPointer][1] < minSizeRequired) {
30 validRoomIds.erase(validRoomIds.find(rooms[roomPointer][0]));
31 roomPointer++;
32 }
33
34 if (roomPointer == numRooms) {
35 break;
36 }
37
38 // Use binary search template to find first room ID >= preferredId
39 vector<int> ids(validRoomIds.begin(), validRoomIds.end());
40 int n = ids.size();
41 int left = 0, right = n - 1;
42 int firstTrueIndex = -1;
43
44 while (left <= right) {
45 int mid = left + (right - left) / 2;
46 if (ids[mid] >= preferredId) {
47 firstTrueIndex = mid;
48 right = mid - 1;
49 } else {
50 left = mid + 1;
51 }
52 }
53
54 if (firstTrueIndex != -1) {
55 result[queryIdx] = ids[firstTrueIndex];
56 }
57
58 if (firstTrueIndex == -1) {
59 result[queryIdx] = ids[n - 1];
60 } else if (firstTrueIndex > 0) {
61 int leftRoomId = ids[firstTrueIndex - 1];
62 if (result[queryIdx] - preferredId >= preferredId - leftRoomId) {
63 result[queryIdx] = leftRoomId;
64 }
65 }
66 }
67
68 return result;
69 }
70};
711function closestRoom(rooms: number[][], queries: number[][]): number[] {
2 const numRooms = rooms.length;
3 const numQueries = queries.length;
4
5 rooms.sort((a, b) => a[1] - b[1]);
6
7 const queryIndices: number[] = Array.from({ length: numQueries }, (_, i) => i);
8 queryIndices.sort((i, j) => queries[i][1] - queries[j][1]);
9
10 const result: number[] = new Array(numQueries).fill(-1);
11 let roomPointer = 0;
12 const validRoomIds = new Set<number>();
13
14 for (const room of rooms) {
15 validRoomIds.add(room[0]);
16 }
17
18 for (const queryIdx of queryIndices) {
19 const preferredId = queries[queryIdx][0];
20 const minSizeRequired = queries[queryIdx][1];
21
22 while (roomPointer < numRooms && rooms[roomPointer][1] < minSizeRequired) {
23 validRoomIds.delete(rooms[roomPointer][0]);
24 roomPointer++;
25 }
26
27 if (roomPointer === numRooms) {
28 break;
29 }
30
31 const ids = Array.from(validRoomIds).sort((a, b) => a - b);
32 const n = ids.length;
33
34 // Binary search template to find first room ID >= preferredId
35 let left = 0;
36 let right = n - 1;
37 let firstTrueIndex = -1;
38
39 while (left <= right) {
40 const mid = Math.floor((left + right) / 2);
41 if (ids[mid] >= preferredId) {
42 firstTrueIndex = mid;
43 right = mid - 1;
44 } else {
45 left = mid + 1;
46 }
47 }
48
49 if (firstTrueIndex !== -1) {
50 result[queryIdx] = ids[firstTrueIndex];
51 }
52
53 if (firstTrueIndex === -1) {
54 result[queryIdx] = ids[n - 1];
55 } else if (firstTrueIndex > 0) {
56 const leftRoomId = ids[firstTrueIndex - 1];
57 if (result[queryIdx] - preferredId >= preferredId - leftRoomId) {
58 result[queryIdx] = leftRoomId;
59 }
60 }
61 }
62
63 return result;
64}
65Time 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 mostnrooms 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. Using Wrong Binary Search Template Variant
Pitfall: Using bisect_left directly without the standard template structure.
Wrong Implementation:
p = valid_room_ids.bisect_left(preferred_id) result[query_idx] = valid_room_ids[p]
Correct Implementation (using template):
left, right = 0, n - 1 first_true_index = -1 while left <= right: mid = (left + right) // 2 if ids[mid] >= preferred_id: first_true_index = mid right = mid - 1 else: left = mid + 1
2. Incorrect Tie-Breaking When Distances Are Equal
Pitfall: Using > instead of >= when comparing distances.
Solution: Use >= to prefer smaller room ID when distances are equal:
if result[query_idx] - preferred_id >= preferred_id - left_room_id: result[query_idx] = left_room_id
3. Not Handling first_true_index = -1
Pitfall: Not handling the case when all room IDs are less than preferred.
Solution: When first_true_index == -1, the closest room is the last one:
if first_true_index == -1: result[query_idx] = ids[n - 1]
4. Processing Queries in Original Order
Pitfall: Processing queries in their original order would require re-adding rooms.
Solution: Process queries sorted by minSize and use an index array to maintain the mapping.
5. Not Checking Bounds Before Accessing Elements
Pitfall: Accessing ids[first_true_index] or ids[first_true_index - 1] without bounds checking.
Solution: Check first_true_index != -1 and first_true_index > 0 before accessing.
Which of the following array represent a max heap?
Recommended Readings
https assets algo monster cover_photos Binary_Search svg Binary Search Intuition Binary search is an efficient array search algorithm It works by narrowing down the search range by half each time If you have looked up a word in a physical dictionary you've already used binary search in real life Let's
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!