题目
给你一棵树,树上有 n 个节点,按从 0 到 n-1 编号。树以父节点数组的形式给出,其中 parent[i] 是节点 i 的父节点。树的根节点是编号为 0 的节点。
请你设计并实现 getKthAncestor(int node, int k) 函数,函数返回节点 node 的第 k 个祖先节点。如果不存在这样的祖先节点,返回 -1 。
树节点的第 k 个祖先节点是从该节点到根节点路径上的第 k 个节点。
题解
预处理
- go[i][x]表示结点x的第 2的i次幂 个祖先结点,即x往上跳 2的i次幂 后达到的结点。
- x跳
2的i次幂
步可以分解为先跳了2的i-1次幂
步,再跳2的i-1次幂
步。 - 从x跳
2的i-1次幂
步后的到达的结点为go[i-1][x],令其为y。 - 再跳
2的i-1次幂
步后到达的结点为go[i-1][y]。 - 则go[i][x] = go[i-1][y] = go[i-1][go[i-1][x]]。
优化
- 把i放在二维数组的第一维,x放在二维数组的第二维。
- 因为i的取值范围很小,n可能很大。
- 将小维度的循环放在外层,可以提高缓存的命中率,进而提高程序运行速度。
i的取值范围是多少?
二叉树有n个结点,树高最多n层,退化为一个长度为n链表,最坏情况下就是求叶结点的第n-1个祖先。
用1 + $log_2{(n-1)}$(向下取整)个二进制位足以表示n-1。
例如8的二进制是1000,16的二进制位10000,8是2的3次幂,16是2的4次幂。
大于等于8小于16的数用4位二进制就可以表示:1000、1001、1010、1011、1100、1101、1110、1111。
所以n-1可以用多少位二进制表示,求$log_2{(n-1)}$后向下取整再加1即可
怎么求第k个祖先?
把k拆解为2的幂的和,即检查k的二进制的每一位是否是1,第i位是1就往上跳2的i次幂
步,这样最多检查(跳)1 + $log_2{(n-1)}$次,因为有效的k最大也就是n-1,n-1用1 + $log_2{(n-1)}$个二进制位可以表示,所以可在O($log_2{n}$)时间内找到第k个祖先
1 | import kotlin.math.log2 |