【力扣roadmap】2940. 找到 Alice 和 Bob 可以相遇的建筑

19 阅读4分钟

题目描述

image.png

这里给出两种做法。第一种是离线查询做法,精巧快速;第二种是在线查询做法,更通用。

思路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,张宇的狗🐶)的答案(这个狗🐶对应下标哪个位置此时已经不重要了,但你可以知道这个狗🐶一定在ji之间,而且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