一、题目
1、题目描述
给你一个下标从 0 开始的正整数数组
heights
,其中heights[i]
表示第i
栋建筑的高度。如果一个人在建筑
i
,且存在i < j
的建筑j
满足heights[i] < heights[j]
,那么这个人可以移动到建筑j
。给你另外一个数组
queries
,其中queries[i] = [ai, bi]
。第i
个查询中,Alice 在建筑ai
,Bob 在建筑bi
。请你能返回一个数组
ans
,其中ans[i]
是第i
个查询中,Alice 和 Bob 可以相遇的 最左边的建筑 。如果对于查询i
,Alice 和 Bob 不能相遇,令ans[i]
为-1
。
2、接口描述
python3
class Solution:
def leftmostBuildingQueries(self, heights: List[int], queries: List[List[int]]) -> List[int]:
3、原题链接
2940. Find Building Where Alice and Bob Can Meet
二、解题报告
1、思路分析
比较直观的思路是:线段树二分 或者 ST表预处理+二分
但是这种直接借助数据结构的方式不利于思维的训练,这种题目往往可以使用离线处理 + 数据结构维护,往难了出可以主席树,整体二分,莫队……比较常见的就是先离线处理查询,再按照某种遍历顺序处理巧妙化解问题
本题考虑两种做法:最小堆 / 单调栈+二分
F1 最小堆
查询(a, b)(a <= b),如果 height[a] < height[b] 或者a == b,那么答案就是b
对于一般情况:
将查询放到右端点b处,记保存每个下标作为右端点的查询,即 找到b右边大于height[a] 的最靠近b的索引
然后我们顺序遍历heights,最小堆保存遍历过的下标的所有以其为右端点的查询
如果堆顶 查询 值小于当前遍历到的height,那么弹出堆顶,维护答案
F2 单调栈+二分
和上一个方法预处理查询方法相同
我们考虑每个查询要找到右边第一个大于指定值的索引
我们可以倒序遍历heights,维护一个单调栈,栈顶到栈底递增,那么遍历过程中,如果有询问,栈顶到栈底第一个大于的就是答案
2、复杂度
时间复杂度: O(n + qlogq)空间复杂度:O(n)
3、代码详解
F1 最小堆
class Solution:
def leftmostBuildingQueries(self, heights: List[int], queries: List[List[int]]) -> List[int]:
n = len(heights)
q = [[] for _ in range(n)]
ans = [-1] * len(queries)
for i, (a, b) in enumerate(queries):
if a > b:
a, b = b, a
if a == b or heights[b] > heights[a]:
ans[i] = b
else:
q[b].append((heights[a], i))
pq = []
for i, x in enumerate(heights):
while pq and pq[0][0] < x:
ans[heappop(pq)[1]] = i
for Q in q[i]:
heappush(pq, Q)
return ans
F2 单调栈二分
class Solution:
def leftmostBuildingQueries(self, heights: List[int], queries: List[List[int]]) -> List[int]:
n = len(heights)
q = [[] for _ in range(n)]
ans = [-1] * len(queries)
for i, (a, b) in enumerate(queries):
if a > b:
a, b = b, a
if a == b or heights[b] > heights[a]:
ans[i] = b
else:
q[b].append((heights[a], i))
st = []
for i in range(len(heights) - 1, -1, -1):
for h, qi in q[i]:
j = bisect_left(st, -h, key=lambda i:-heights[i])
if j:
ans[qi] = st[j - 1]
while st and heights[i] >= heights[st[-1]]:
st.pop()
st.append(i)
return ans