一个酒店里有 n
个房间,这些房间用二维整数数组 rooms
表示,其中 rooms[i] = [roomIdi, sizei]
表示有一个房间号为 roomIdi
的房间且它的面积为 sizei
。每一个房间号 roomIdi
保证是 独一无二 的。
同时给你 k
个查询,用二维数组 queries
表示,其中 queries[j] = [preferredj, minSizej]
。第j
个查询的答案是满足如下条件的房间 id
:
房间的面积 至少 为 minSizej
,且
abs(id - preferredj)
的值 最小 ,其中 abs(x)
是 x
的绝对值。
如果差的绝对值有 相等 的,选择 最小 的 id
。如果 没有满足条件的房间 ,答案为 -1
。
请你返回长度为 k
的数组 answer
,其中 __answer[j]
为第 j
个查询的结果。
示例 1:
**输入:** rooms = [[2,2],[1,2],[3,2]], queries = [[3,1],[3,3],[5,2]]
**输出:** [3,-1,3]
**解释:** 查询的答案如下:
查询 [3,1] :房间 3 的面积为 2 ,大于等于 1 ,且号码是最接近 3 的,为 abs(3 - 3) = 0 ,所以答案为 3 。
查询 [3,3] :没有房间的面积至少为 3 ,所以答案为 -1 。
查询 [5,2] :房间 3 的面积为 2 ,大于等于 2 ,且号码是最接近 5 的,为 abs(3 - 5) = 2 ,所以答案为 3 。
示例 2:
**输入:** rooms = [[1,4],[2,3],[3,5],[4,1],[5,2]], queries = [[2,3],[2,4],[2,5]]
**输出:** [2,1,3]
**解释:** 查询的答案如下:
查询 [2,3] :房间 2 的面积为 3 ,大于等于 3 ,且号码是最接近的,为 abs(2 - 2) = 0 ,所以答案为 2 。
查询 [2,4] :房间 1 和 3 的面积都至少为 4 ,答案为 1 因为它房间编号更小。
查询 [2,5] :房间 3 是唯一面积大于等于 5 的,所以答案为 3 。
提示:
n == rooms.length
1 <= n <= 105
k == queries.length
1 <= k <= 104
1 <= roomIdi, preferredj <= 107
1 <= sizei, minSizej <= 107
方法一:离线算法 提示 1
如果我们可以将给定的房间和询问重新排序,那么是否可以使得问题更加容易解决?
提示 2
我们可以将房间以及询问都看成一个「事件」,如果我们将这些「事件」按照大小(房间的 size 或者询问的 minSize)进行降序排序,那么:
如果我们遇到一个表示房间的「事件」,那么可以将该房间的 roomId 加入某一「数据结构」中;
如果我们遇到一个表示询问的「事件」,那么需要在「数据结构」中寻找与 preferred 最接近的 roomId。
提示 3
你能想出一种合适的「数据结构」吗?
思路与算法
我们使用「有序集合」作为提示中的「数据结构」。
根据提示 2,我们将每一个房间以及询问对应一个「事件」,放入数组中进行降序排序。随后我们遍历这些「事件」:
如果我们遇到一个表示房间的「事件」,那么将该该房间的 roomId 加入「有序集合」中;
如果我们遇到一个表示询问的「事件」,那么答案即为「有序集合」中与询问的 preferred 最接近的那个 roomId。在大部分语言的「有序集合」的 API 中,提供了例如「在有序集合中查找最小的大于等于 x 的元素」或者「在有序集合中查找最小的严格大于 x 的元素」,我们可以利用这些 API 找出「有序集合」中与 preferred 最接近的两个元素,其中一个小于 preferred,另一个大于等于 preferred。通过比较这两个元素,我们即可得到该询问对应的答案。
细节
如果不同类型的「事件」的位置相同,那么我们应当按照先处理表示房间的「事件」,再处理表示询问的「事件」,这是因为房间的 size 只要大于等于询问的 minSize 就是满足要求的。
代码
[sol1-C++] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 struct Event { int type; int size; int id; int origin; Event (int _type, int _size, int _id, int _origin): type (_type), size (_size), id (_id), origin (_origin) {} bool operator < (const Event& that) const { return size > that.size || (size == that.size && type < that.type); } }; class Solution {public : vector<int > closestRoom (vector<vector<int >>& rooms, vector<vector<int >>& queries) { int m = rooms.size (); int n = queries.size (); vector<Event> events; for (int i = 0 ; i < m; ++i) { events.emplace_back (0 , rooms[i][1 ], rooms[i][0 ], i); } for (int i = 0 ; i < n; ++i) { events.emplace_back (1 , queries[i][1 ], queries[i][0 ], i); } sort (events.begin (), events.end ()); vector<int > ans (n, -1 ) ; set<int > valid; for (const auto & event: events) { if (event.type == 0 ) { valid.insert (event.id); } else { int dist = INT_MAX; auto it = valid.lower_bound (event.id); if (it != valid.end () && *it - event.id < dist) { dist = *it - event.id; ans[event.origin] = *it; } if (it != valid.begin ()) { it = prev (it); if (event.id - *it <= dist) { dist = event.id - *it; ans[event.origin] = *it; } } } } return ans; } };
[sol1-Python3] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 class Event : """ op: 事件的类型,0 表示房间,1 表示询问 size: 房间的 size 或者询问的 minSize idx: 房间的 roomId 或者询问的 preferred origin: 房间在数组 room 中的原始编号或者询问在数组 queries 中的原始编号 """ def __init__ (self, op: int , size: int , idx: int , origin: int ): self.op = op self.size = size self.idx = idx self.origin = origin """ 自定义比较函数,按照事件的 size 降序排序 如果 size 相同,优先考虑房间 """ def __lt__ (self, other: "Event" ) -> bool : return self.size > other.size or (self.size == other.size and self.op < other.op) class Solution : def closestRoom (self, rooms: List [List [int ]], queries: List [List [int ]] ) -> List [int ]: n = len (queries) events = list () for i, (roomId, size) in enumerate (rooms): events.append(Event(0 , size, roomId, i)) for i, (minSize, preferred) in enumerate (queries): events.append(Event(1 , preferred, minSize, i)) events.sort() ans = [-1 ] * n valid = sortedcontainers.SortedList() for event in events: if event.op == 0 : valid.add(event.idx) else : dist = float ("inf" ) x = valid.bisect_left(event.idx) if x != len (valid) and valid[x] - event.idx < dist: dist = valid[x] - event.idx ans[event.origin] = valid[x] if x != 0 : x -= 1 if event.idx - valid[x] <= dist: dist = event.idx - valid[x] ans[event.origin] = valid[x] return ans
复杂度分析
时间复杂度:O((n+q) \log (n+q)),其中 n 是数组 rooms 的长度,q 是数组 queries 的长度。「事件」的数量为 n+q = O(n+q),因此需要 O((n+q) \log (n+q)) 的时间进行排序。在这之后,我们需要 O(n+q) 的时间对事件进行遍历,而对有序集合进行操作的单次时间复杂度为 O(\log n),总时间复杂度为 O((n+q) \log n),在渐进意义下小于前者,可以忽略。
空间复杂度:O(n+q)。我们需要 O(n+q) 的空间存储「事件」,以及 O(n) 的空间分配给有序集合,因此总空间复杂度为 O(n+q)。