题目
给你一棵 完全二叉树 的根节点 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;
}
};