<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>

class SegNode():
    def __init__(self,val, left, right):
        self.val = val
        self.left = left
        self.right = right


class Solution:


    def leftmostBuildingQueries(self, heights: List[int], queries: List[List[int]]) -> List[int]:
        # segment tree implementation, 
        def construct_segment_tree(l, r):

            nonlocal heights
            if l == r:
                return SegNode(((l,r),heights[l]), None, None)
            
            else:
                mid = (l + r) // 2
                left, right = construct_segment_tree(l, mid), construct_segment_tree(mid + 1, r)
                return SegNode(((l,r),max(left.val[1], right.val[1])), left, right)

        # wanna see the constructed seg tree, refer the commented out code below, 

        """
        tr = construct_segment_tree(0, len(heights) - 1)
        def recur(root):
            if not root:
                return 
            print(root.val)
            recur(root.right)
            recur(root.left)
        recur(tr)
        """

        def overlap(a,b,l,r):

            """

            1 -- partial-overlap
            2 -- complete-overlap
            3 -- no-overlap

            """
            if a <= l and b >= r:
                return 2
            elif r < a or b < l:
                return 3
            else:
                return 1
        
        root = construct_segment_tree(0, len(heights) - 1)
        max_between_intervals_cache = {}

        def max_between_intervals(a,b,root):
            # search on the segment tree with cache
        
            l, r = root.val[0]
            overlap_status = overlap(a,b,l,r)

            if overlap_status == 2:
                return root.val[1]
            elif overlap_status == 3:
                return -inf
            else:
                return max(max_between_intervals(a,b,root.left), max_between_intervals(a,b,root.right))


        def binary_search(i, req):
            nonlocal root
            l, r = i, len(heights) - 1

            while l < r:
                mid = (l + r) // 2
                if (l,mid) in max_between_intervals_cache:
                    mx_b_interval = max_between_intervals_cache[(l,mid)]
                else:
                    mx_b_interval = max_between_intervals(l,mid, root)
                    max_between_intervals_cache[(l,mid)] = mx_b_interval

                if mx_b_interval > req:
                    r = mid
                else:
                    l = mid + 1
            
            return r if heights[r] > req else -1
        
        answer = []
        root = construct_segment_tree(0, len(heights) - 1)

        for l, r in queries:
            l, r = min(l,r), max(l,r)
            if heights[l] < heights[r] or l == r:
                answer.append(r)
            
            else:
                answer.append(binary_search(r + 1, heights[l]))
            
        
        return answer
83100