文章目录
- 写在前面
- Tag
- 题目来源
- 解题思路
- 方法一:遍历统计
- 方法二:二分查找+位运算
- 写在最后
写在前面
本专栏专注于分析与讲解【面试经典150】算法,两到三天更新一篇文章,欢迎催更……
专栏内容以分析题目为主,并附带一些对于本题涉及到的数据结构等内容进行回顾与总结,文章结构大致如下,部分内容会有增删:
- Tag:介绍本题牵涉到的知识点、数据结构;
- 题目来源:贴上题目的链接,方便大家查找题目并完成练习;
- 题目解读:复述题目(确保自己真的理解题目意思),并强调一些题目重点信息;
- 解题思路:介绍一些解题思路,每种解题思路包括思路讲解、实现代码以及复杂度分析;
- 知识回忆:针对今天介绍的题目中的重点内容、数据结构进行回顾总结。
Tag
【完全二叉树】【二分+位运算】
题目来源
222. 完全二叉树的节点个数

解题思路
方法一:遍历统计
思路
使用前序、中序、后序以及层序遍历中的任何一种遍历方法,即可统计完全二叉树的节点个数。这个方法也适应一切二叉树。
以下是前序遍历方法的迭代实现。
代码
/**
* 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 {
private:
int res = 0;
public:
void preOrder(TreeNode* root) {
if (!root) {
return;
}
res += 1;
preOrder(root->left);
preOrder(root->right);
}
int countNodes(TreeNode* root) {
preOrder(root);
return res;
}
};
复杂度分析
时间复杂度: O ( n ) O(n) O(n), n n n 是完全二叉树的节点数。
空间复杂度: O ( n ) O(n) O(n)。
方法二:二分查找+位运算
方法一没有用到 完全二叉树 这个条件,仅仅是使用一般的方法就可以解答。
现在考虑如何充分利用完全二叉树这个条件。完全二叉树除了最后一层可能不满,其余每一层都是满的。这样的话,我们可以直接计算二叉树的前 h-1
层的节点总数(设完全二叉树一共 h
层)。当
0
≤
i
<
h
0 \le i < h
0≤i<h 时,第
i
i
i 层包含
2
i
2^i
2i 个节点,前 h-1
层的节点共计:
∑ i = 0 h − 1 2 i \sum_{i=0}^{h-1}{2^i} i=0∑h−12i
最后一层的节点数怎么求呢?因为是完全二叉树,所以最后一层的节点数范围为
[
1
,
2
h
]
[1, 2^h]
[1,2h]。于是对于最大层数为 h
的完全二叉树,节点个数一定在
[
2
h
,
2
h
+
1
−
1
]
[2^h, 2^{h+1}-1]
[2h,2h+1−1] 范围内。
此时可以通过二分查找确定完全二叉树的节点个数。
具体地,根据节点个数范围的上下界得到当前需要判断的节点个数 k
,如果第 k
个节点存在,则节点个数一定 大于等于 k
;如果第 k
个节点不存在,则节点个数一定小于 k
,由此可以将查找范围缩小一半,直到得到节点个数。
位运算
如何判断第 k
个节点是否存在呢?如果第 k
个节点位于第 h
层,则 k
的二进制表示包含 h+1
(0 到 h) 位,其中最高位是 1
。其余各位从高到低表示从根节点到第 k
个节点的路径,0
表示移动到左子节点,1
表示移动到右子节点。通过位运算得到第 k
个节点对应的路径,判断该路径对应的节点是否存在,即可判断第 k
个节点是否存在。
示例 1 的完全二叉树以及二进制位表示以及如何查找指定节点如图所示。

代码
/**
* 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:
bool exits(TreeNode* root, int depth, int mid) {
int bits = 1 << (depth - 1); //
TreeNode* node = root;
while(node != nullptr && bits > 0) {
if ((bits & mid)) {
node = node->right;
}
else {
node = node->left;
}
bits >>= 1;
}
return node != nullptr;
}
int countNodes(TreeNode* root) {
if (root == nullptr) {
return 0;
}
// 求完全二叉树最大深度 h
int depth = 0;
TreeNode* node = root;
while(node->left != nullptr) {
++depth;
node = node->left;
}
// 确定二分法的二分范围 2^{h} 到 2^{h+1}-1
int low = 1 << depth;
int high = (1 << (depth + 1)) - 1;
while (low < high) {
int mid = (high - low + 1) / 2 + low;
if (exits(root, depth, mid)) {
low = mid;
}
else {
high = mid - 1;
}
}
return low;
}
};
复杂度分析
时间复杂度:
O
(
l
o
g
2
n
)
O(log^2n)
O(log2n),其中
n
n
n 是完全二叉树的节点数。首先需要
O
(
h
)
O(h)
O(h) 的时间得到完全二叉树的最大层数,其中
h
h
h 是完全二叉树的最大层数。
使用二分查找确定节点个数时,需要查找的次数为
O
(
l
o
g
2
h
)
=
O
(
h
)
O(log^2h)=O(h)
O(log2h)=O(h),每次查找需要遍历从根节点开始的一条长度为
h
h
h 的路径,需要
O
(
h
)
O(h)
O(h) 的时间,因此二分查找的总时间复杂度是
O
(
h
2
)
O(h^2)
O(h2),由于本题是二叉搜索树所以有
O
(
h
)
=
O
(
l
o
g
n
)
O(h)=O(logn)
O(h)=O(logn),因方法二的时间复杂度为
O
(
l
o
g
2
n
)
O(log^2n)
O(log2n)。
空间复杂度: O ( 1 ) O(1) O(1)。
写在最后
如果您发现文章有任何错误或者对文章有任何疑问,欢迎私信博主或者在评论区指出 💬💬💬。
如果大家有更优的时间、空间复杂度的方法,欢迎评论区交流。
最后,感谢您的阅读,如果有所收获的话可以给我点一个 👍 哦。