Hard
题目描述
给你一棵树,树上有 n 个节点,按从 0 到 n-1 编号。树以父节点数组的形式给出,其中 parent[i] 是节点 i 的父节点。树的根节点是编号为 0 的节点。
树节点的第 k 个祖先节点是从该节点到根节点路径上的第 k 个节点。
实现 TreeAncestor 类:
- TreeAncestor(int n, int[] parent) 使用树中节点的数量和父节点数组初始化对象。
- int getKthAncestor(int node, int k) 返回节点 node 的第 k 个祖先节点。如果不存在这样的祖先节点,返回 -1 。
示例 1:
输入:
["TreeAncestor", "getKthAncestor", "getKthAncestor", "getKthAncestor"]
[[7, [-1, 0, 0, 1, 1, 2, 2]], [3, 1], [5, 2], [6, 3]]
输出:
[null, 1, 0, -1]
解释:
TreeAncestor treeAncestor = new TreeAncestor(7, [-1, 0, 0, 1, 1, 2, 2]);
treeAncestor.getKthAncestor(3, 1); // 返回 1 ,它是 3 的父节点
treeAncestor.getKthAncestor(5, 2); // 返回 0 ,它是 5 的祖父节点
treeAncestor.getKthAncestor(6, 3); // 返回 -1 因为不存在满足要求的祖先节点
提示:
- 1 <= k <= n <= 5 * 10^4
- parent.length == n
- parent[0] == -1
- 对于所有的 0 < i < n ,0 <= parent[i] < n 成立
- 0 <= node < n
- 至多查询 5 * 10^4 次
解题思路
这道题需要高效地查询树中节点的第k个祖先。如果每次查询都从当前节点开始向上遍历k步,时间复杂度会达到O(k),在最坏情况下可能导致超时。
核心思路是使用倍增法(Binary Lifting):
倍增法是一种预处理技巧,我们预先计算每个节点的第2^0、2^1、2^2、…个祖先。具体来说:
- up[i][j] 表示节点i的第2^j个祖先
- up[i][0] = parent[i](第1个祖先就是父节点)
- up[i][j] = up[up[i][j-1]][j-1](第2^j个祖先 = 第2^(j-1)个祖先的第2^(j-1)个祖先)
查询时,我们将k表示为二进制形式,然后根据k的二进制位来决定向上跳跃的步数。例如,如果k=5(二进制101),我们先跳2^2=4步,再跳2^0=1步。
时间复杂度分析:
- 预处理:O(n log n)
- 每次查询:O(log k)
这种方法能够在对数时间内完成查询,非常适合处理大量查询的场景。
代码实现
class TreeAncestor {
private:
vector<vector<int>> up;
int LOG;
public:
TreeAncestor(int n, vector<int>& parent) {
LOG = ceil(log2(n)) + 1;
up.assign(n, vector<int>(LOG, -1));
// 初始化第1个祖先(2^0 = 1)
for (int i = 0; i < n; i++) {
up[i][0] = parent[i];
}
// 倍增预处理
for (int j = 1; j < LOG; j++) {
for (int i = 0; i < n; i++) {
if (up[i][j-1] != -1) {
up[i][j] = up[up[i][j-1]][j-1];
}
}
}
}
int getKthAncestor(int node, int k) {
for (int i = 0; i < LOG; i++) {
if ((k >> i) & 1) {
node = up[node][i];
if (node == -1) break;
}
}
return node;
}
};
class TreeAncestor:
def __init__(self, n: int, parent: List[int]):
import math
self.LOG = int(math.log2(n)) + 1
self.up = [[-1] * self.LOG for _ in range(n)]
# 初始化第1个祖先(2^0 = 1)
for i in range(n):
self.up[i][0] = parent[i]
# 倍增预处理
for j in range(1, self.LOG):
for i in range(n):
if self.up[i][j-1] != -1:
self.up[i][j] = self.up[self.up[i][j-1]][j-1]
def getKthAncestor(self, node: int, k: int) -> int:
for i in range(self.LOG):
if (k >> i) & 1:
node = self.up[node][i]
if node == -1:
break
return node
public class TreeAncestor {
private int[,] up;
private int LOG;
public TreeAncestor(int n, int[] parent) {
LOG = (int)Math.Ceiling(Math.Log2(n)) + 1;
up = new int[n, LOG];
// 初始化为-1
for (int i = 0; i < n; i++) {
for (int j = 0; j < LOG; j++) {
up[i, j] = -1;
}
}
// 初始化第1个祖先(2^0 = 1)
for (int i = 0; i < n; i++) {
up[i, 0] = parent[i];
}
// 倍增预处理
for (int j = 1; j < LOG; j++) {
for (int i = 0; i < n; i++) {
if (up[i, j-1] != -1) {
up[i, j] = up[up[i, j-1], j-1];
}
}
}
}
public int GetKthAncestor(int node, int k) {
for (int i = 0; i < LOG; i++) {
if (((k >> i) & 1) == 1) {
node = up[node, i];
if (node == -1) break;
}
}
return node;
}
}
var TreeAncestor = function(n, parent) {
this.LOG = 16;
this.up = Array(n).fill().map(() => Array(this.LOG).fill(-1));
for (let i = 0; i < n; i++) {
this.up[i][0] = parent[i];
}
for (let j = 1; j < this.LOG; j++) {
for (let i = 0; i < n; i++) {
if (this.up[i][j-1] !== -1) {
this.up[i][j] = this.up[this.up[i][j-1]][j-1];
}
}
}
};
TreeAncestor.prototype.getKthAncestor = function(node, k) {
for (let i = 0; i < this.LOG; i++) {
if ((k >> i) & 1) {
node = this.up[node][i];
if (node === -1) return -1;
}
}
return node;
};
复杂度分析
| 操作 | 时间复杂度 | 空间复杂度 |
|---|---|---|
| 初始化 | O(n log n) | O(n log n) |
| 查询 | O(log k) | O(1) |