P222 完全二叉树的节点个数
题目链接:[222. 完全二叉树的节点个数](https://leetcode-cn.com/problems/count-complete-tree-nodes/).
题目描述
给出一个完全二叉树,求出该树的节点个数。
说明:
完全二叉树的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h h h 层,则该层包含 1 2 h 1\text{~}2^h 1 2h 个节点。
示例:
输入:
1
/ \
2 3
/ \ /
4 5 6
输出: 6
题解
方法一:DFS
思路
对于任意二叉树,都可以通过BFS或者DFS计算节点个数
算法
class Solution {
public:
int countNodes(TreeNode* root) {
if(!root){
return 0;
}
return countNodes(root->left)+countNodes(root->right)+1;
}
};
复杂度分析
假设二叉树的节点个数为 n n n
- 时间复杂度: O ( n ) O(n) O(n)
- 空间复杂度: O ( n ) O(n) O(n)
方法二:BFS
思路
对于任意二叉树,都可以通过BFS或者DFS计算节点个数
算法
class Solution {
public:
int countNodes(TreeNode* root) {
if(!root){
return 0;
}
int count=0;
queue<TreeNode*> nodeQ;
nodeQ.push(root);
while(!nodeQ.empty()){
TreeNode* node=nodeQ.front();
if(node->left){
nodeQ.push(node->left);
}
if(node->right){
nodeQ.push(node->right);
}
nodeQ.pop();
++count;
}
return count;
}
};
复杂度分析
假设二叉树的节点个数为 n n n
- 时间复杂度: O ( n ) O(n) O(n)
- 空间复杂度: O ( n ) O(n) O(n)
方法三:二分查找 + 位运算
思路
BFS与DFS适用于任意二叉树中节点计数问题,既然要求完全二叉树的节点个数,那么我们应该结合完全二叉树的性质来求解。
完全二叉树的最下面一层的最左节点是叶节点,通过访问这个叶节点可以得出完全二叉树的深度,具体做法是,从根节点开始,迭代访问左子女节点,直到左子女节点为空,即得完全二叉树深度。
在完全二叉树中,若根节点位于第 0 0 0 层,最大层数为 h h h(深度 − 1 -1 −1),那么
- 最少节点数为 2 h 2^{h} 2h
- 最多节点数为 2 h + 1 − 1 2^{h+1}-1 2h+1−1
所以,若确定了完全二叉树的最大层数,那么节点个数在范围 [ 2 h , 2 h + 1 − 1 ] [2^{h},2^{h+1}-1] [2h,2h+1−1] 内,可以使用二分查找来确定节点个数,由当前节点个数范围取中值 k k k,判断节点 k k k(从根节点为 1 1 1 按层序为节点编号,节点 k k k 即编号为 k k k)是否存在,然后继续二分查找。
现在需要做的就是如何判断节点
k
k
k 是否存在,先说具体做法,位于第
i
i
i 层的节点,其编号转为二进制后位数为
i
+
1
i+1
i+1 位,最高位为
1
1
1,其它从高到低二进制位决定了从根节点到该节点的路径,即若为
1
1
1 则访问右子女节点,若为
0
0
0 则访问左子女节点。
12 12 12 对应二进制 1100 1100 1100,最高位 1 1 1 忽略, 0100 & 1100 0100\&1100 0100&1100 得第 2 2 2 位为 1 1 1,从根节点出发,访问右子女结点; 0010 & 1100 0010\&1100 0010&1100 得第 3 3 3 位为 0 0 0,再访问左子女结点; 0001 & 1100 0001\&1100 0001&1100 得第 4 4 4 位为 0 0 0,再访问左子女结点,这样就得到了从根节点编号为 12 12 12 的节点的路径。
既然我们知道了从根节点到节点 k k k 的路径,那么就可以判断节点 k k k 是否存在了。
算法
class Solution {
public:
int getDepth(TreeNode* root){ //求完全二叉树最大层数(根结点位于第0层)
if(!root){
return 0;
}
int depth=0;
while(root->left){
++depth;
root=root->left;
}
return depth;
}
bool exist(TreeNode* root,int depth,int k){ //位运算判断结点k是否存在
int mode=1<<(depth-1);
while(root&&mode){
if(k&mode){
root=root->right;
}
else{
root=root->left;
}
mode>>=1;
}
return root;
}
int countNodes(TreeNode* root) {
if(!root){
return 0;
}
int depth=getDepth(root);
int left=1<<depth;
int right=(1<<(depth+1))-1;
while(left<right){
int mid=left+((right-left+1)>>1); //注意运算符优先级
if(exist(root,depth,mid)){
left=mid;
}
else{
right=mid-1;
}
}
return left;
}
};
在二分时取 m i d = l e f t + ( ( r i g h t − l e f t + 1 ) > > 1 ) mid=left+((right-left+1)>>1) mid=left+((right−left+1)>>1)
- 不直接用 ( l e f t + r i g h t + 1 ) > > 1 (left+right+1)>>1 (left+right+1)>>1 是因为防止越界
- + 1 +1 +1 是为了防止当 r i g h t = l e f t + 1 right=left+1 right=left+1 时出现死循环
复杂度分析
假设完全二叉树的节点个数为 n n n,最大层数为 h = ⌊ log n ⌋ h=\lfloor\log{n}\rfloor h=⌊logn⌋
- 时间复杂度: O ( log 2 n ) O(\log^2{n}) O(log2n),第 h h h 层的最大结点数为 2 h 2^h 2h,所以进行二分查找的次数为 log 2 h = h \log{2^h}=h log2h=h,在每次查找中,需要从根节点开始,访问 h h h 个节点,所以时间复杂度为 O ( h 2 ) O(h^2) O(h2),又由于 h = ⌊ log n ⌋ h=\lfloor\log{n}\rfloor h=⌊logn⌋,所以时间复杂度为 O ( log 2 n ) O(\log^2{n}) O(log2n)
- 空间复杂度: O ( 1 ) O(1) O(1)