<p>You are given the <code>root</code> of a <strong>binary tree</strong> with <code>n</code> nodes. Each node is assigned a unique value from <code>1</code> to <code>n</code>. You are also given an array <code>queries</code> of size <code>m</code>.</p>

<p>You have to perform <code>m</code> <strong>independent</strong> queries on the tree where in the <code>i<sup>th</sup></code> query you do the following:</p>

<ul>
	<li><strong>Remove</strong> the subtree rooted at the node with the value <code>queries[i]</code> from the tree. It is <strong>guaranteed</strong> that <code>queries[i]</code> will <strong>not</strong> be equal to the value of the root.</li>
</ul>

<p>Return <em>an array </em><code>answer</code><em> of size </em><code>m</code><em> where </em><code>answer[i]</code><em> is the height of the tree after performing the </em><code>i<sup>th</sup></code><em> query</em>.</p>

<p><strong>Note</strong>:</p>

<ul>
	<li>The queries are independent, so the tree returns to its <strong>initial</strong> state after each query.</li>
	<li>The height of a tree is the <strong>number of edges in the longest simple path</strong> from the root to some node in the tree.</li>
</ul>

<p>&nbsp;</p>
<p><strong class="example">Example 1:</strong></p>
<img alt="" src="https://assets.leetcode.com/uploads/2022/09/07/binaryytreeedrawio-1.png" style="width: 495px; height: 281px;" />
<pre>
<strong>Input:</strong> root = [1,3,4,2,null,6,5,null,null,null,null,null,7], queries = [4]
<strong>Output:</strong> [2]
<strong>Explanation:</strong> The diagram above shows the tree after removing the subtree rooted at node with value 4.
The height of the tree is 2 (The path 1 -&gt; 3 -&gt; 2).
</pre>

<p><strong class="example">Example 2:</strong></p>
<img alt="" src="https://assets.leetcode.com/uploads/2022/09/07/binaryytreeedrawio-2.png" style="width: 301px; height: 284px;" />
<pre>
<strong>Input:</strong> root = [5,8,9,2,1,3,7,4,6], queries = [3,2,4,8]
<strong>Output:</strong> [3,2,3,2]
<strong>Explanation:</strong> We have the following queries:
- Removing the subtree rooted at node with value 3. The height of the tree becomes 3 (The path 5 -&gt; 8 -&gt; 2 -&gt; 4).
- Removing the subtree rooted at node with value 2. The height of the tree becomes 2 (The path 5 -&gt; 8 -&gt; 1).
- Removing the subtree rooted at node with value 4. The height of the tree becomes 3 (The path 5 -&gt; 8 -&gt; 2 -&gt; 6).
- Removing the subtree rooted at node with value 8. The height of the tree becomes 2 (The path 5 -&gt; 9 -&gt; 3).
</pre>

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

<ul>
	<li>The number of nodes in the tree is <code>n</code>.</li>
	<li><code>2 &lt;= n &lt;= 10<sup>5</sup></code></li>
	<li><code>1 &lt;= Node.val &lt;= n</code></li>
	<li>All the values in the tree are <strong>unique</strong>.</li>
	<li><code>m == queries.length</code></li>
	<li><code>1 &lt;= m &lt;= min(n, 10<sup>4</sup>)</code></li>
	<li><code>1 &lt;= queries[i] &lt;= n</code></li>
	<li><code>queries[i] != root.val</code></li>
</ul>

'''
Input:
TreeNode root, List[int] queries

Output:
List[int] height_after_independent_query

Constraints:
- Q1: Can empty/invalid input?
-- @ empty root OR empty queries? A: Yes
- Q2: Is it a binary tree? A: Yes
- Q3: Is it guaranteened queries[i] in root? A: Yes
- Q4: Is all node.val unique in root? A: Yes
- Q5: Is the each query independent (i.e. not affect the tree itself)? A: Yes
- Q6: Is height 0-based? A: Yes

Test Cases:
- root = [1,3,4,2,null,6,5,null,null,null,null,null,7], queries = [4], return [2]
- root = [5,8,9,2,1,3,7,4,6], queries = [3,2,4,8], return [3,2,3,2]
- root = [5], queries = [5], return 0
- root = [], queries = [], return 0

[Solution 1: 3 Hashmaps for val_to_depth, val_to_height, depth_to_heights]
Idea:
# 1. Init 3 hashmaps
# - val_to_depth # { node_val -> depth } (top-down)
# - val_to_height # { node_val -> height } (bottom-up)
# - depth_to_heights # { depth -> [height1, height2...] }

# 2. Do dfs_height(root, depth=0) to record val_to_depth, val_to_height, depth_to_heights

# 3. To get the max height of a depth-level, descending sort the heights in depth_to_heights

# result = []
# 4. for each node_val query, get depth and height
# -- [If: the depth only has 1 height, i.e. one subtree]
# ---- append "depth - 1" to result # as the only subtree is remove
# -- [Else if: the depth-level's max height is the node_val's subtree height]
# ---- append "depeth-level's 2nd-max height + depth" to result
# -- [Else:]
# ---- apepend "depeth-level's max height + depth" to result
# return result


##### helpers
#def dfs_height(root, depth, val_to_depth, val_to_height, depth_to_heights)
# [If node == null]
# -- return -1
# left_height = dfs_height(root.left, depth+1...)
# right_height = dfs_height(root.right, depth+1...)
# height = 1 + max(left_height, right_height)
# record val_to_depth, val_to_height, depth_to_heights
# return height

Time: O(nlong + q)
Space: O(n + q)


[Solution TLE: Recursion - Get height excluding val]
Idea:
#def dfs(root, removal_val):
# [if node == null or node.val == removal_val]
# -- return -1
# ...

# For each query:
# -- append dfs(root, removal_val) to result

Time: O(q * n)
Space: O(q + h)
'''


class Solution:
    def treeQueries(self, root: TreeNode, queries: List[int]) -> List[int]:
        solution1 = SolutionHashmapsForDepthHeightRelations()
        solution2 = SolutionBruteForceTLE()
        
        return solution1.treeQueries(root, queries)


class SolutionHashmapsForDepthHeightRelations:
    def treeQueries(self, root: TreeNode, queries: List[int]) -> List[int]:
        # 1. Init 3 hashmaps
        # - val_to_depth # { node_val -> depth } (top-down)
        # - val_to_height # { node_val -> height } (bottom-up)
        # - depth_to_heights # { depth -> [height1, height2...] }
        val_to_depth = {}
        val_to_height = {}
        depth_to_heights = defaultdict(list)

        # 2. Do DFS(root, depth=0) to record val_to_depth, val_to_height, depth_to_heights
        _ = self.dfs_height(root, 0, val_to_depth, val_to_height, depth_to_heights)

        # 3. To get the max height of a depth-level, descending sort the heights in depth_to_heights
        for _, heights in depth_to_heights.items():
            heights.sort(reverse=True)

        # result = []
        # 4. for each node_val query, get depth and height
        # -- [If: the depth only has 1 height, i.e. one subtree]
        # ---- append "depth - 1" to result # as the only subtree is remove
        # -- [Else if: the depth-level's max height is the node_val's subtree height]
        # ---- append "depeth-level's 2nd-max height + depth" to result
        # -- [Else:]
        # ---- apepend "depeth-level's max height + depth" to result
        # return result
        result = []
        for query_val in queries:
            depth = val_to_depth[query_val]
            height = val_to_height[query_val]

            if len(depth_to_heights[depth]) == 1:
                result.append(depth - 1)
            elif depth_to_heights[depth][0] == height:
                result.append(depth_to_heights[depth][1] + depth)
            else:
                result.append(depth_to_heights[depth][0] + depth)

        return result
    
    
    '''
    helpers
    '''
    ##### helpers
    #def dfs_height(root, depth, val_to_depth, val_to_height, depth_to_heights)
    # [If node == null]
    # -- return -1
    # left_height = dfs_height(root.left, depth+1...)
    # right_height = dfs_height(root.right, depth+1...)
    # height = 1 + max(left_height, right_height)
    # record val_to_depth, val_to_height, depth_to_heights
    # return height
    def dfs_height(self, node: TreeNode, depth: int, val_to_depth: dict[int, int], val_to_height: dict[int, int], depth_to_heights: dict[int, List[int]]) -> int:
        if not node:
            return -1
        
        left_height = self.dfs_height(node.left, depth + 1, val_to_depth, val_to_height, depth_to_heights)
        right_height = self.dfs_height(node.right, depth + 1, val_to_depth, val_to_height, depth_to_heights)
        height = 1 + max(left_height, right_height)

        val_to_depth[node.val] = depth
        val_to_height[node.val] = height
        depth_to_heights[depth].append(height)

        return height


class SolutionBruteForceTLE:
    def treeQueries(self, root: Optional[TreeNode], queries: List[int]) -> List[int]:
        # For each query:
        # -- append dfs(root, removal_val) to result
        result = []
        for query in queries:
            result.append(self.get_height_excluding_val(root, query))
        return result


    '''
    helper
    '''
    #def dfs(root, removal_val):
    # [if node == null or node.val == removal_val]
    # -- return -1
    # ...
    def get_height_excluding_val(self, node: TreeNode, removal_val: int) -> int:
        if not node or node.val == removal_val:
            return -1
        
        left_subtree_height = self.get_height_excluding_val(node.left, removal_val)
        right_subtree_height = self.get_height_excluding_val(node.right, removal_val)
        
        return 1 + max(left_subtree_height, right_subtree_height)
55311