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

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right


class Solution:
    def treeQueries(self, root: Optional[TreeNode], queries: List[int]) -> List[int]:

        #  list per depth of the nodes in this depth, and their height
        nodes_per_depth = {}  # depth: (height ,val)
        height_per_node = {}  # val: height
        node_to_depth = {}  # val: depth
        node_to_height = {}

        def get_level_heights(node: TreeNode, node_depth):
            if node is None:
                return
            left_height = 0
            right_height = 0
            if node.left:
                get_level_heights(node.left, node_depth + 1)
                left_height = height_per_node[node.left.val]
            if node.right:
                get_level_heights(node.right, node_depth + 1)
                right_height = height_per_node[node.right.val]
            node_height = 1 + max(left_height, right_height)
            height_per_node[node.val] = node_height
            if node_depth not in nodes_per_depth:
                nodes_per_depth[node_depth] = []
            nodes_per_depth[node_depth].append((node_height, node.val))
            node_to_depth[node.val] = node_depth
            node_to_height[node.val] = node_height

        get_level_heights(root, 0)

        # for depth level, find the two hieghst nodes
        heighest_per_depth = {}  # depth: [most_high_val, second_high_val]
        for depth, nodes in nodes_per_depth.items():
            max_height_node = max(nodes)  # (height, val)
            heighest_per_depth[depth] = [max_height_node[1]]
            if len(nodes) > 1:
                # get the second hieghst
                second_heighest = max(
                    [node for node in nodes if node != max_height_node]
                )  # (hieght, val)
                heighest_per_depth[depth].append(second_heighest[1])

        print(heighest_per_depth)
        ans = []
        original_height = node_to_height[root.val]
        for query in queries:
            query_depth = node_to_depth[query]
            query_height = node_to_height[query]
            highest_node_in_depth = heighest_per_depth[query_depth][0]
            if query != highest_node_in_depth:
                # no change to tree height
                ans.append(original_height - 1)
            elif len(heighest_per_depth[query_depth]) > 1:
                # query is the highest but after its removal the tree height is set by the second hieghts
                second_heightst_node = heighest_per_depth[query_depth][1]
                ans.append(query_depth + node_to_height[second_heightst_node] - 1)
            else:
                # no additional node in this level
                ans.append(query_depth - 1)
        return ans


# # Definition for a binary tree node.
# # class TreeNode:
# #     def __init__(self, val=0, left=None, right=None):
# #         self.val = val
# #         self.left = left
# #         self.right = right

# def get_tree_height(node: TreeNode, deleted_node_val: int) -> int:
#     if node is None:
#         return 0
#     left_height = 0
#     right_height = 0
#     if node.left and node.left.val != deleted_node_val:
#         left_height = get_tree_height(node.left, deleted_node_val)
#     if node.right and node.right.val != deleted_node_val:
#         right_height = get_tree_height(node.right, deleted_node_val)
#     return 1 + max(left_height, right_height)


# class Solution:
#     def treeQueries(self, root: Optional[TreeNode], queries: List[int]) -> List[int]:
#         # idea 2: for each node in the tree - find how many childs it has in each depth of the original tree
#         ans = []
#         prev_ans = {}
#         for query in queries:
#             if query in prev_ans:
#                 cur_ans =  prev_ans[query]
#             else:
#                 cur_ans = get_tree_height(root, query) - 1
#             prev_ans[query] = cur_ans
#             ans.append(cur_ans)
#         return ans

70821