题目详见https://leetcode.cn/problems/kth-ancestor-of-a-tree-node/
首先我们提出如下四个问题,随后我们会逐个解释这四个问题,希望这四个问题可以帮助理解该题:
- 为什么Log的值为16?
- 最后的ancestors数组是怎样的?
- 为什么只存储第 2 n 2^n 2n 个祖先,那我们需要找第3个祖先怎么办?
- 为什么说类似于动态规划?如何理解其状态转移方程?
1. 为什么Log的值为16?
- 题目没有说所有的树都是二叉树或者满二叉树,所以可能是奇奇怪怪的树。
- 题目限制节点个数最多为 5 ∗ 1 0 n 5*10^n 5∗10n 个。
- n层满二叉树共有 2 n − 1 2^n - 1 2n−1 个节点,这里画个图用等比数列求和公式自己推一下。
- 16层满二叉树共有65535 > 50000个节点,16层就够用了。
为什么不是三叉树,为什么不是n叉树
- 无论是几叉树,我们在存储的时候都会按照满n叉树来存储。设想我们采取一个只有右子树的16层树,他最后一个节点的坐标也是为65535(从0开始),只不过其余节点在题目中都是-1。
- 因此二叉树是能够达到最深地方的树。(一共100斤的人,瘦了肯定高,略显富态肯定稍微变矮)
2. 最后的ancestors数组是怎样的?
官方给的用例我并不是很喜欢,因为他并没有让我们很好的理解getKthAncestor(6, 3)
这个函数,因为他返回-1。
我的用例如下:
[“TreeAncestor”,“getKthAncestor”,“getKthAncestor”,“getKthAncestor”]
[[15,[-1,0,0,1,1,2,2,3,3,4,4,5,5,6,6]],[3,1],[5,2],[6,3]]
// 是一个四层的满二叉树,ancestors数组如下:
[
[-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], // 0号节点
[0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], // 1号节点
[0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
[1, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
[1, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
[2, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
[2, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
[3, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
[3, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
[4, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
[4, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
[5, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
[5, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
[6, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
[6, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1] // 14号节点
]
3. 为什么只存储第 2 n 2^n 2n 个祖先,那我们需要找第3个祖先怎么办?
通过观察上面的ancestors数组,我们发现14号节点也就是最后一个节点,只存储了6,2这两个祖先节点,其最原始的祖先节点0竟然没有存?
这也就对应到问题3. 为什么只存储第
2
n
2^n
2n 个祖先,那我们需要找第3个祖先怎么办?
终于开始算法了,如何理解这里的倍增?
我认为倍增的基本思想是预处理出每个节点的第
2
i
2^i
2i个祖先,然后使用这些信息来快速查询任何节点的任何祖先。在这个代码中,ancestors[node][j]
表示节点node
的第
2
j
2^j
2j个祖先。
那么为什么要用每个节点的第 2 i 2^i 2i个祖先呢?
下面我举一个让学过一些计算机知识的人恍然大悟的例子:
13表示为二进制的话是:1101
如果要找第13个祖先,那么就找当前节点的第1个、第4个和第8个祖先就OK了
(句子补全一下)如果要找第13个祖先,那么就找当前节点的第1个、(第1个节点的)第4个和(第1个节点的第4个祖先的)第8个祖先就OK了
同理我们如果要找第三个祖先
3表示为二进制的话是:11
如果要找第3个祖先,那么就找当前节点的第1个和第2个祖先就OK了
4. 为什么说类似于动态规划?如何理解其状态转移方程?
为什么说类似于动态规划:我们是从低到高以此构建其
2
i
2^i
2i个祖先,本质上与动态规划十分相似。
如何理解状态转移方程:
a
n
c
e
s
t
o
r
s
[
i
]
[
j
]
=
a
n
c
e
s
t
o
r
s
[
a
n
c
e
s
t
o
r
s
[
i
]
[
j
−
1
]
]
[
j
−
1
]
ancestors[i][j]=ancestors[ancestors[i][j−1]][j−1]
ancestors[i][j]=ancestors[ancestors[i][j−1]][j−1]
取上面的一个数组为例:
[6, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1]
表示
第
2
0
2^0
20个祖先是6
第
2
1
2^1
21个祖先是2
第
2
2
2^2
22个祖先是-1
…
a
n
c
e
s
t
o
r
s
[
14
]
[
1
]
=
a
n
c
e
s
t
o
r
s
[
a
n
c
e
s
t
o
r
s
[
14
]
[
0
]
]
[
0
]
ancestors[14][1]=ancestors[ancestors[14][0]][0]
ancestors[14][1]=ancestors[ancestors[14][0]][0]
下标为14的节点的第
2
1
2^1
21个祖先 =
a
n
c
e
s
t
o
r
s
[
6
]
[
0
]
ancestors[6][0]
ancestors[6][0] = 第六个节点的第
2
0
2^0
20个祖先(该过程是有个计数器的,到了k就停了,所以不会一直找下去)
注释代码
class TreeAncestor {
public:
constexpr static int Log = 16; // 请见问题1
vector<vector<int>> ancestors;
TreeAncestor(int n, vector<int>& parent) {
ancestors = vector<vector<int>>(n, vector<int>(Log, -1)); // 全部初始化为-1
for(int i = 0; i < n; i++){
ancestors[i][0] = parent[i]; // 每个节点的第2的0次方个祖先先设为他的祖先,这个parent题目已知
}
for(int j = 1; j < Log; j++){
for(int i = 0; i < n; i++){
if(ancestors[i][j-1] != -1){
// 详见问题4
ancestors[i][j] = ancestors[ancestors[i][j-1]][j-1];
}
}
}
}
int getKthAncestor(int node, int k) {
for(int j = 0; j < Log; j++){
// k >> j 是一个右移操作,意味着将 k 的二进制表示向右移动 j 位。
// 例如,如果 k 是 8(在二进制中表示为 1000),j 是 2,那么 k >> j 就是 2(在二进制中表示为 10)
// 然后,& 1 是一个位与操作,它将 k >> j 的结果与 1 进行比较。
// 如果 k >> j 的最低位(即右边的位)是 1,那么结果就是 1,否则结果就是 0。
if((k >> j) & 1){ // 所以这里实现的功能就是看看要不要找第2的i次方个祖先,结合13=1101理解
node = ancestors[node][j];
if(node == -1){
return -1;
}
}
}
return node;
}
};
/**
* Your TreeAncestor object will be instantiated and called as such:
* TreeAncestor* obj = new TreeAncestor(n, parent);
* int param_1 = obj->getKthAncestor(node,k);
*/
笔者也在新手学习期中,所写的内容主要与大家交流学习使用,如有发现任何问题敬请指正!