<p>You are given a <strong>0-indexed</strong> array <code>heights</code> of positive integers, where <code>heights[i]</code> represents the height of the <code>i<sup>th</sup></code> building.</p>

<p>If a person is in building <code>i</code>, they can move to any other building <code>j</code> if and only if <code>i &lt; j</code> and <code>heights[i] &lt; heights[j]</code>.</p>

<p>You are also given another array <code>queries</code> where <code>queries[i] = [a<sub>i</sub>, b<sub>i</sub>]</code>. On the <code>i<sup>th</sup></code> query, Alice is in building <code>a<sub>i</sub></code> while Bob is in building <code>b<sub>i</sub></code>.</p>

<p>Return <em>an array</em> <code>ans</code> <em>where</em> <code>ans[i]</code> <em>is <strong>the index of the leftmost building</strong> where Alice and Bob can meet on the</em> <code>i<sup>th</sup></code> <em>query</em>. <em>If Alice and Bob cannot move to a common building on query</em> <code>i</code>, <em>set</em> <code>ans[i]</code> <em>to</em> <code>-1</code>.</p>

<p>&nbsp;</p>
<p><strong class="example">Example 1:</strong></p>

<pre>
<strong>Input:</strong> heights = [6,4,8,5,2,7], queries = [[0,1],[0,3],[2,4],[3,4],[2,2]]
<strong>Output:</strong> [2,5,-1,5,2]
<strong>Explanation:</strong> In the first query, Alice and Bob can move to building 2 since heights[0] &lt; heights[2] and heights[1] &lt; heights[2]. 
In the second query, Alice and Bob can move to building 5 since heights[0] &lt; heights[5] and heights[3] &lt; heights[5]. 
In the third query, Alice cannot meet Bob since Alice cannot move to any other building.
In the fourth query, Alice and Bob can move to building 5 since heights[3] &lt; heights[5] and heights[4] &lt; heights[5].
In the fifth query, Alice and Bob are already in the same building.  
For ans[i] != -1, It can be shown that ans[i] is the leftmost building where Alice and Bob can meet.
For ans[i] == -1, It can be shown that there is no building where Alice and Bob can meet.
</pre>

<p><strong class="example">Example 2:</strong></p>

<pre>
<strong>Input:</strong> heights = [5,3,8,2,6,1,4,6], queries = [[0,7],[3,5],[5,2],[3,0],[1,6]]
<strong>Output:</strong> [7,6,-1,4,6]
<strong>Explanation:</strong> In the first query, Alice can directly move to Bob&#39;s building since heights[0] &lt; heights[7].
In the second query, Alice and Bob can move to building 6 since heights[3] &lt; heights[6] and heights[5] &lt; heights[6].
In the third query, Alice cannot meet Bob since Bob cannot move to any other building.
In the fourth query, Alice and Bob can move to building 4 since heights[3] &lt; heights[4] and heights[0] &lt; heights[4].
In the fifth query, Alice can directly move to Bob&#39;s building since heights[1] &lt; heights[6].
For ans[i] != -1, It can be shown that ans[i] is the leftmost building where Alice and Bob can meet.
For ans[i] == -1, It can be shown that there is no building where Alice and Bob can meet.

</pre>

<p>&nbsp;</p>
<p><strong>Constraints:</strong></p>

<ul>
	<li><code>1 &lt;= heights.length &lt;= 5 * 10<sup>4</sup></code></li>
	<li><code>1 &lt;= heights[i] &lt;= 10<sup>9</sup></code></li>
	<li><code>1 &lt;= queries.length &lt;= 5 * 10<sup>4</sup></code></li>
	<li><code>queries[i] = [a<sub>i</sub>, b<sub>i</sub>]</code></li>
	<li><code>0 &lt;= a<sub>i</sub>, b<sub>i</sub> &lt;= heights.length - 1</code></li>
</ul>

from collections import deque
class Solution:
    def leftmostBuildingQueries(self, heights: List[int], queries: List[List[int]]) -> List[int]:
        # if they're on the same building great!
        # it's possible that only one travels to the right and the other stationary
        # in which case, this implies that the greatest value UP to b_j must be greater
        # than a_i so that way a_i can even jump there
        # if there's a descrepency, that is greatest[b_j] is heights[a_j] then thyey both must move
        # we want the FURTHEST to the left. we know the LOWEr bound of who it must be that is greatest[b_j]+1
        # if it's not there, we want the next GREATER.
        # this data structure is a problem! while binary search works great on it.
        # But it doesn't factor in that the LARgest value may not be in that range such that a and b
        # MUST be able to jump to it. May we perhaps use a segment tree? Such that we find the left
        # most largest number. But we still want the smallest of that largest number. 
        # greatest = [-1]*n
        # greatest[0] = heights[0]
        # for i in range(1,n):
        #     greatest[i] = max(greatest[i-1], heights[i])

        # I had the right idea! just iterate through the sorted queries in reverse
        # kinda like the heights problem! The stack will be the same as sorted
        # we don't need any fancy trees but I've never done one of these queries problems
        # where they're not time dependent, but each one bounds the greatest property 
        # therefore iterate backwards!
        stack = deque()
        # noramlize queries
        for i, [a, b] in enumerate(queries):
            queries[i] = [min(a,b), max(a,b)]
        # Monotonic stack, with its opening on its left side. Will be monotonically INCREASING
        # stackPtr will 'allow' more elements from the heights in AS queries allow it.
        # by iterating over queries backwards, we're leveraging the 'shared' aspect of 
        # large elements as we 'expand' our left bound! so simple like DP or prefixSum paradigms
        stackPtr = len(heights)-1
        def refreshStack(bound: int):
            nonlocal stackPtr
            while stackPtr > bound:
                while len(stack) > 0 and heights[stackPtr] >= stack[0][0]:
                    stack.popleft()
                stack.appendleft((heights[stackPtr], stackPtr))
                stackPtr-=1

        # finds leftmost target, if not present returns 'next greater number'
        def bisectLeftNextGreater(target: int)->int:
            
            lo, hi = 0, len(stack)-1
            while lo <= hi:
                mid = (lo+hi)>>1
                # if the value doesn't exist, finding greater elements will trigger
                # hi to move to other side. therefore final iteration, lo will point to next greater than
                if stack[mid][0] >= target:
                    hi = mid-1
                else:
                    lo = mid+1
            return stack[lo][1] if lo < len(stack) else -1

        # leverage that these queries are on a fixed array, BUT we can fix our bounds issue
        # by simply updating the current nextGreatest value in stack/queue
        queriesWithIdx = sorted(enumerate(queries), key = lambda x: x[1][1], reverse=True)
        ans, cache = [42] * len(queries), {}
        
        for idx, [a, b] in queriesWithIdx:
            if (a,b) not in cache:
                # no jumps from either
                if a == b:
                    cache[(a,b)] = b
                # 'a' can just jump to b
                elif heights[a] < heights[b]:
                    cache[(a,b)] = b
                # they both must jump to a higher common master!
                else:
                    refreshStack(b)
                    cache[(a,b)] = bisectLeftNextGreater(heights[a]+1)
            ans[idx] = cache[(a,b)]
        return ans
44500