题目描述
这里给出两种做法。第一种是离线查询做法,精巧快速;第二种是在线查询做法,更通用。
思路1
离线查询的思路比较精巧。
首先先理清楚,对于a和b两个位置(不妨a<=b),有高度heights[a]和heights[b].
- 假如
heights[a] < heights[b],那么显然,a可以向右直接跳到b处,因此对于当前的查询(a,b),答案就是b。 - 假如
heights[a] > heights[b],那么此时当前查询的答案应该是位置b向后看第一个大于heights[a]的位置。问题是,这咋求?
针对刚刚抛出来的问题,我们思考一下,“位置b向后看第一个大于heights[a]的位置”,嘶~ 好像heights[b]对答案的求解不那么重要呢。但是位置b这个位置信息倒是很重要。
我们不妨,将(heights[a],query_idx)存储到qs[b]这个列表中,表示b位置左边有个heights[a](而且隐形信息是heights[a] > heights[b], 但这对答案求解不重要你知道一下就好),同时捎带上query_idx信息,这对离线查询很重要。
我们使用一个小根堆,存储前i个位置的所有qs(qs[0],qs[1]...qs[i-1]全都放进小根堆中),当你遍历heights数组到第i个位置时,假如你发现当前小根堆的堆顶(heights[j] , query_idx)(其中j一定小于i,因为你放进小根堆的是qs[0~i-1]的信息)的heights[j]小于heights[i],这说明什么?!这说明对于一个query_idx来说,它查询了下标pair(j,张宇的狗🐶)的答案(这个狗🐶对应下标哪个位置此时已经不重要了,但你可以知道这个狗🐶一定在j和i之间,而且heights[张宇的狗🐶] < heights[j]) ,而且当前我们第一次遇到了heights[j] < heights[i], 说明下标i就是对于query_idx的答案!所以ans[query_idx]=i.
完事你继续翻找堆顶尝试填写答案数组。直到堆顶元素比当前heights[i]更大,说明当前堆顶指望着更大的元素出现,所以当前循环的填写答案任务结束,再把qs[i]装进小根堆。
btw,其实你在当前循环填写ans数组之前就把qs[i]全装进小根堆不影响答案,但是为什么我们最好是先填完答案再装填小根堆呢?因为qs[i]表示i位置左边比heights[i]大的高度元素,这些相关查询的答案包不是i!所以你这么着急装填小根堆干啥?
代码
class Solution:
def leftmostBuildingQueries(self, heights: List[int], queries: List[List[int]]) -> List[int]:
ans = [-1] * len(queries)
q_list = [[] for _ in range(len(heights))]
for i , (a,b) in enumerate(queries) :
if a > b :
a , b = b , a
if a == b or heights[a] < heights[b] :
ans[i] = b
else :
q_list[b].append((heights[a],i))
hp = []
for i , h in enumerate(heights) :
while hp and hp[0][0] < h :
ans[hp[0][1]] = i
heappop(hp)
for q in q_list[i]:
heappush(hp,q)
return ans
思路2
线段树。这就暴力很多了。我们开四倍区间的长度创建完全二叉树(因为存在这种情况:完全二叉树最后一行只存储了1个有效元素,倒数第二层存储了n个元素————最后一行2n个节点,倒数第二行n个节点,倒数第二行往上所有层总和n个节点,总共4n个节点)
我们使用线段树维护区间最大值。记得build完全二叉树后将最大值信息上推。
然后查询答案怎么查?我们需要查询b位置后面的比heights[a]大的位置(同思路1的描述,只是查询方法换成了暴力的线段树)
其实逻辑也简单。写一个query的深搜:
- 先检查当前区间最大值是不是比目标大,如果不是那还搜个鸡毛
- 如果当前已经聚焦到了一个合法节点,直接返回这个位置的下标
- 检查当前左子树是否存在合法区间(mid >= L 则说明左子树有合法的搜索区间),那么尝试搜一下左子树。返回了非-1的合法下标可以直接返回答案了。不然(左子树无合法搜索区间 L > mid 或者 搜了左子树发现没有大于目标的下标)就去搜右子树。
代码
class Solution:
def leftmostBuildingQueries(self, heights: List[int], queries: List[List[int]]) -> List[int]:
n = len(heights)
mx = [0] * (4 * n + 1)
def build(idx: int , l : int , r : int ) :
if l == r :
mx[idx] = heights[l]
return
mid = l + (r - l) // 2
build(idx * 2 , l , mid)
build(idx * 2 + 1 , mid + 1 , r)
mx[idx] = max(mx[idx*2], mx[idx*2+1])
def query(idx : int , l : int , r : int , L : int , v : int) -> int :
if mx[idx] <= v :
return -1 # invalid pos
if l == r :
return l
mid = l + (r - l) // 2
if mid >= L and (pos := query(idx * 2 , l , mid , L , v)) != -1 :
return pos # 左子树包含部分合法搜索区间(合法搜索区间为[L~n-1]) 优先从左子树寻找答案
return query(idx * 2 + 1 , mid + 1 , r , L , v)
build(1,0,n-1)
ans = []
for (a,b) in queries:
if a > b :
a , b = b , a
if a == b or heights[a] < heights[b] :
ans.append(b)
else :
ans.append(query(1,0,n-1 , b+1 , heights[a]))
return ans