Leetcode-1483-树节点的第 K 个祖先-c++

题目详见https://leetcode.cn/problems/kth-ancestor-of-a-tree-node/

首先我们提出如下四个问题,随后我们会逐个解释这四个问题,希望这四个问题可以帮助理解该题:

  1. 为什么Log的值为16?
  2. 最后的ancestors数组是怎样的?
  3. 为什么只存储第 2 n 2^n 2n 个祖先,那我们需要找第3个祖先怎么办?
  4. 为什么说类似于动态规划?如何理解其状态转移方程?

1. 为什么Log的值为16?

  • 题目没有说所有的树都是二叉树或者满二叉树,所以可能是奇奇怪怪的树。
  • 题目限制节点个数最多为 5 ∗ 1 0 n 5*10^n 510n 个。
  • n层满二叉树共有 2 n − 1 2^n - 1 2n1 个节点,这里画个图用等比数列求和公式自己推一下。
  • 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][j1]][j1]
取上面的一个数组为例:
[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);
 */

笔者也在新手学习期中,所写的内容主要与大家交流学习使用,如有发现任何问题敬请指正!

  • 48
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值