Hard
题目描述
给你一个有 n 个节点的二叉树的根节点 root,每个节点都有一个从 1 到 n 的唯一值。同时给你一个大小为 m 的数组 queries。
你需要在树上执行 m 个独立的查询,在第 i 个查询中,你需要:
- 从树中移除以值为 queries[i] 的节点为根的子树。保证 queries[i] 不等于根节点的值。
返回一个大小为 m 的数组 answer,其中 answer[i] 是执行第 i 个查询后树的高度。
注意:
- 查询是独立的,所以在每次查询后,树都会恢复到初始状态。
- 树的高度是从根节点到树中某个节点的最长简单路径中的边数。
示例 1:
输入: root = [1,3,4,2,null,6,5,null,null,null,null,null,7], queries = [4]
输出: [2]
解释: 上图显示了移除以节点4为根的子树后的树。
树的高度是2(路径 1 -> 3 -> 2)。
示例 2:
输入: root = [5,8,9,2,1,3,7,4,6], queries = [3,2,4,8]
输出: [3,2,3,2]
解释: 我们有以下查询:
- 移除以节点3为根的子树。树的高度变为3(路径 5 -> 8 -> 2 -> 4)。
- 移除以节点2为根的子树。树的高度变为2(路径 5 -> 8 -> 1)。
- 移除以节点4为根的子树。树的高度变为3(路径 5 -> 8 -> 2 -> 6)。
- 移除以节点8为根的子树。树的高度变为2(路径 5 -> 9 -> 3)。
约束条件:
- 树中节点数为 n
- 2 <= n <= 10⁵
- 1 <= Node.val <= n
- 树中所有值都是唯一的
- m == queries.length
- 1 <= m <= min(n, 10⁴)
- 1 <= queries[i] <= n
- queries[i] != root.val
解题思路
这道题的关键在于预处理,避免对每个查询都重新计算整棵树。
思路分析:
核心思想是预计算每个节点被移除后的树高度。对于任意节点的移除,新的树高度等于从根节点出发,不经过被移除节点的最长路径。
算法步骤:
- 第一次DFS:计算每个节点的深度(距离根节点的距离)和每个子树的高度
- 第二次DFS:自顶向下计算每个节点被移除后的答案
- 对于当前节点,考虑其父节点能提供的最大深度
- 同时考虑兄弟子树能提供的最大深度
- 这两者的最大值就是移除当前节点后的树高度
关键观察:
- 当移除某个节点时,剩余树的高度来源于两部分:
- 从根到该节点父节点的路径继续向其他子树延伸
- 该节点的兄弟子树
通过两次DFS遍历,第一次自底向上计算子树信息,第二次自顶向下传递父节点信息,可以在O(n)时间内预处理出所有答案,每个查询O(1)响应。
代码实现
class Solution {
public:
vector<int> treeQueries(TreeNode* root, vector<int>& queries) {
unordered_map<int, int> nodeDepth, subtreeHeight, answer;
// First DFS: calculate depth and subtree height
function<int(TreeNode*, int)> dfs1 = [&](TreeNode* node, int depth) -> int {
if (!node) return -1;
nodeDepth[node->val] = depth;
int leftHeight = dfs1(node->left, depth + 1);
int rightHeight = dfs1(node->right, depth + 1);
return subtreeHeight[node->val] = max(leftHeight, rightHeight) + 1;
};
// Second DFS: calculate answer for each node
function<void(TreeNode*, int)> dfs2 = [&](TreeNode* node, int maxDepthFromParent) {
if (!node) return;
answer[node->val] = maxDepthFromParent;
int leftHeight = node->left ? subtreeHeight[node->left->val] : -1;
int rightHeight = node->right ? subtreeHeight[node->right->val] : -1;
// For left child: max depth comes from parent or right sibling
dfs2(node->left, max(maxDepthFromParent, nodeDepth[node->val] + 1 + rightHeight));
// For right child: max depth comes from parent or left sibling
dfs2(node->right, max(maxDepthFromParent, nodeDepth[node->val] + 1 + leftHeight));
};
dfs1(root, 0);
dfs2(root, 0);
vector<int> result;
for (int query : queries) {
result.push_back(answer[query]);
}
return result;
}
};
class Solution:
def treeQueries(self, root: Optional[TreeNode], queries: List[int]) -> List[int]:
node_depth = {}
subtree_height = {}
answer = {}
def dfs1(node, depth):
if not node:
return -1
node_depth[node.val] = depth
left_height = dfs1(node.left, depth + 1)
right_height = dfs1(node.right, depth + 1)
subtree_height[node.val] = max(left_height, right_height) + 1
return subtree_height[node.val]
def dfs2(node, max_depth_from_parent):
if not node:
return
answer[node.val] = max_depth_from_parent
left_height = subtree_height.get(node.left.val, -1) if node.left else -1
right_height = subtree_height.get(node.right.val, -1) if node.right else -1
dfs2(node.left, max(max_depth_from_parent, node_depth[node.val] + 1 + right_height))
dfs2(node.right, max(max_depth_from_parent, node_depth[node.val] + 1 + left_height))
dfs1(root, 0)
dfs2(root, 0)
return [answer[query] for query in queries]
public class Solution {
private Dictionary<int, int> nodeDepth = new Dictionary<int, int>();
private Dictionary<int, int> subtreeHeight = new Dictionary<int, int>();
private Dictionary<int, int> answer = new Dictionary<int, int>();
public int[] TreeQueries(TreeNode root, int[] queries) {
Dfs1(root, 0);
Dfs2(root, 0);
int[] result = new int[queries.Length];
for (int i = 0; i < queries.Length; i++) {
result[i] = answer[queries[i]];
}
return result;
}
private int Dfs1(TreeNode node, int depth) {
if (node == null) return -1;
nodeDepth[node.val] = depth;
int leftHeight = Dfs1(node.left, depth + 1);
int rightHeight = Dfs1(node.right, depth + 1);
return subtreeHeight[node.val] = Math.Max(leftHeight, rightHeight) + 1;
}
private void Dfs2(TreeNode node, int maxDepthFromParent) {
if (node == null) return;
answer[node.val] = maxDepthFromParent;
int leftHeight = node.left != null ? subtreeHeight[node.left.val] : -1;
int rightHeight = node.right != null ? subtreeHeight[node.right.val] : -1;
Dfs2(node.left, Math.Max(maxDepthFromParent, nodeDepth[node.val] + 1 + rightHeight));
Dfs2(node.right, Math.Max(maxDepthFromParent, nodeDepth[node.val] + 1 + leftHeight));
}
}
var treeQueries = function(root, queries) {
const nodeDepth = new Map();
const subtreeHeight = new Map();
const answer = new Map();
function dfs1(node, depth) {
if (!node) return -1;
nodeDepth.set(node.val, depth);
const leftHeight = dfs1(node.left, depth + 1);
const rightHeight = dfs1(node.right, depth + 1);
const height = Math.max(leftHeight, rightHeight) + 1;
subtreeHeight.set(node.val, height);
return height;
}
function dfs2(node, maxDepthFromParent) {
if (!node) return;
answer.set(node.val, maxDepthFromParent);
const leftHeight = node.left ? subtreeHeight.get(node.left.val) : -1;
const rightHeight = node.right ? subtreeHeight.get(node.right.val) : -1;
dfs2(node.left, Math.max(maxDepthFromParent, nodeDepth.get(node.val) + 1 + rightHeight));
dfs2(node.right, Math.max(maxDepthFromParent, nodeDepth.get(node.val) + 1 + leftHeight));
}
dfs1(root, 0);
dfs2(root, 0);
return queries.map(query => answer.get(query));
};
复杂度分析
| 复杂度类型 | 复杂度 |
|---|---|
| 时间复杂度 | O(n + m) |
| 空间复杂度 | O(n) |
说明:n是树中节点数,m是查询数。预处理需要O(n)时间遍历整棵树两次,每个查询O(1)时间响应。空间复杂度主要用于存储哈希表和递归栈空间。