Kth Ancestor of a Tree Node 树节点的第 K 个祖先
问题描述:
给你一棵树,树上有 n 个节点,按从 0 到 n-1 编号。树以父节点数组的形式给出,其中 parent[i] 是节点 i 的父节点。树的根节点是编号为 0 的节点。
树节点的第 k 个祖先节点是从该节点到根节点路径上的第 k 个节点
k,n范围[1,50000]
parent[0] == -1
表示编号为 0
的节点是根节点
最多调用次数50000
分析
树的结构是通过数组给出,parent[i]表示的是节点的父节点,而且树可能是一个N叉树,每个节点都有一个唯一编号,要求从指定编号的节点出发直到根节点的路径上第k个祖先节点。
最通常可以想到的就是构建一个非线性结构树,从提供的数组可以方便的找到每个节点的父节点。
方向没问题,但是以要求的数据规模,这个思路是无法承受的。
如果是从叶子出发向上寻找,最差的情况下,树退化成为链表,找一次k,就要遍历k个父节点,如果K很大的情况下,时间复杂度也是很庞大的。
因此就要有些手段加速寻找。
因为已经得到了pa数组,要查询节点i的第k个祖先,第一个祖先就是pa[i],第二个祖先为pa[pa[i]],第二个祖先为pa[pa[pa[i]]],…。
i
−
>
p
a
[
i
]
−
>
p
a
[
p
a
[
i
]
]
−
>
p
a
[
p
a
[
p
a
[
i
]
]
]
−
>
…
i->pa[i]->pa[pa[i]]->pa[pa[pa[i]]]->\dots
i−>pa[i]−>pa[pa[i]]−>pa[pa[pa[i]]]−>…
既然可以直接找到父节点,那么在
O
(
1
)
O(1)
O(1)时间内找到父节点的父节点,也是可以的,前提是需要预处理。
定义
f
[
i
]
[
j
]
f[i][j]
f[i][j]表示 节点i的第
2
j
2^j
2j个祖先节点的编号,
f
[
i
]
[
0
]
f[i][0]
f[i][0]就是i的第一个祖先节点即父节点,
f
[
i
]
[
1
]
f[i][1]
f[i][1]为第二个祖先节点即父节点的父节点。
如果当第
2
j
2^j
2j个祖先节点不存在,那么就是-1.
因此查找i的第k个祖先节点,就是把k用二进制表示,对
i
i
i预处理出它的每一个
2
j
2^j
2j祖先编号。
由于n的最大规模是50000,所以j最大开到16基本可以覆盖。
BTW,这个思路也叫倍增 Binary Lifting
代码
int[][] f;
int maxPow;
public TreeAncestor(int n, int[] parent) {
// log_base_2(n)
maxPow = (int) (Math.log(n) / Math.log(2)) + 1;
f = new int[n][maxPow];
for(int i=0;i<n;i++){
f[i][0] = parent[i];
}
for (int i = 0; i<n ; i++) {
for (int j = 1; j < maxPow; j++) {
int p = f[i][j-1];
f[i][j] = p==-1?-1:f[p][j-1];
}
}
}
public int getKthAncestor(int node, int k) {
int pos =0, res = node;
while(k>0&& res!=-1){
if(pos>=f[res].length) return -1;
if((k&1)!=0){
res = f[res][pos];
}
k>>=1;pos++;
}
return res;
}
时间复杂度 O ( N L o g N ) O(NLogN) O(NLogN)
空间复杂度: O ( N L o g N ) O(NLogN) O(NLogN)
单次查询 O ( L o g N ) O(LogN) O(LogN)
Tag
Array
BinaryLifting