LC222. 完全二叉树的节点个数

题目

给你一棵 完全二叉树 的根节点 root ,求出该树的节点个数。

完全二叉树 的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层,则该层包含 1~ 2h 个节点。
提示:

树中节点的数目范围是[0, 5 * 104]
0 <= Node.val <= 5 * 104
题目数据保证输入的树是 完全二叉树

进阶:遍历树来统计节点是一种时间复杂度为 O(n) 的简单解决方案。你可以设计一个更快的算法吗?

思路

BFS遍历

遍历属于暴力解法,时间复杂度为O(n)

二分查找+位运算

要设计更快的算法,我们需要利用完全二叉树的性质。
对于一个h层的满二叉树,我们可以得到节点个数为2h-1。而对于一个h层的完全二叉树,我们可以得到节点个数为2h-1-1+k。其中k为最后一层叶子节点的个数。

对于一个完全二叉树,从根节点开始为依次标为第i个节点:
在这里插入图片描述
将每个序号转化为二进制,可以得到一个很神奇的规律:

1:1
2:10;3:11
4:100,5:101;6:110

可以发现:对于每一个节点序号,从序号二进制表示的从左向右第二位开始,0和1代表了从根节点到该节点的指向。0代表了左节点,1代表了右节点。
比如:

6:110
第二位:1,代表此时应选择根节点的右节点(即,3)
接着从左到右第三位:0,代表此时应选择当前节点的左节点(即,6)

因此通过位运算,我们从序号就能找到某个节点位置。

并且一层中从左到右节点序号是递增排列的,此时可以采用二分查找:

  • 假设最后一层是满节点,则左指针left在序号2h-1处,右指针right在序号2h-1处。
  • 通过mid=(left+right+1)/2判断当前节点是否存在。
  • 如果存在,left左移。否则,right右移。

代码

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    int countNodes(TreeNode* root) {
        if(root==nullptr) return 0;   //判断二叉树为空
        TreeNode *node = root;
        int h = 1;
        while(node->left){   //找到二叉树最左端,算高度
            h++;
            node = node->left;
        }
        int left = 1<<(h-1), right = (1<<h)-1;
        int mid;
        while(left<right){
            mid = left+(right-left+1)/2;   
            //加1是为了防止left和right相邻,mid每次算的都一样,进入死循环
            
            if(exit(root, h, mid))  //判断第mid个节点是否存在
                left = mid;     //left不为mid+1,是因为如果mid就是最后一个节点,mid+1无意义了
            else
                right = mid-1;
        }
        return left;
    }

    bool exit(TreeNode*root, int h, int mid){
        int f = 1<<(h-2);   //f用于和mid每一位相与
        while(f&&root){
            if((mid&f)==0)   //判断mid从左第二位往后每位值是否为0
                root = root->left;
            else
                root = root->right;
            f>>=1;   //f左移一位,用于判断mid下一位
        }
        return root!=nullptr;
    }
};
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值