222. 完全二叉树的节点个数
给出一个完全二叉树,求出该树的节点个数。
说明:
- 完全二叉树的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层,则该层包含 1~ 2h 个节点。
示例:
输入:
1
/ \
2 3
/ \ /
4 5 6
输出: 6
节点类如下:
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) { val = x; }
}
方法一:不讲武德
对于任意二叉树,bfs 或者 dfs 扫一遍即可
时间复杂度和空间复杂度都为 O(n)。
public int countNodes(TreeNode root) {
if (root == null){
return 0;
}
int left = countNodes(root.left);
int right = countNodes(root.right);
return left + right + 1;
}
方法二:二分查找 + 位运算
ps:参考官方解答,确实高招~
对于「完全二叉树」,假设层数为 h ,最后一层的节点个数在 1 到 2h 之间,对于 h >= 1,h - 1 层共有 2h - 1 个节点。
所以 h 层,节点总个数在 [ 2h, 2h+1 - 1]。设 k 为总节点个数,第 h 层最多有 2h个节点,二分查找每次淘汰一半的数据,所以只需 h 次操作便能找出 k。
具体做法是每次找出中间值 mid ,判断 mid 是否在最后一层。「在」则抛弃 mid 前面一半的数据,「不在」则抛弃 mid 后面一半的数据。
下面便是精髓,如何判断某个节点(中间值)是否在第 h 层?
如图所示(图片来自官方解答):判断第 12 个节点是否在最后一层。
12 的二进制为 1100,去除首位得到 100,1 代表右移到右节点, 0 代表左移到左节点。从根节点开始「右→左→ 左」就能找到第 12 个节点。如果该节点为 null 则总数小于当前节点,反之总数大于等于当前节点。
通过位运算,我们经过 h 次操作(根据二进制位左移或右移)就能判断某个节点是否在第 h 层。
public int countNodes1(TreeNode root) {
if (root == null){
return 0;
}
// 算出层数
int level = 0;
TreeNode tmp = root;
while (tmp.left != null) {
level++;
tmp = tmp.left;
}
// low:最后一层第一个节点对应的个数, high:最后一层最后一个节点对应的个数
int low = 1 << level;
int high = (1 << (level + 1)) - 1;
// 通过二分查找找出目标节点
while (low < high) {
int mid = (high - low + 1) / 2 + low;
if (exists(root, level, mid)) {
low = mid;
} else {
high = mid - 1;
}
}
return low;
}
// 根据二进制位进行左移右移,返回该节点是否存在
public boolean exists(TreeNode root, int level, int pos) {
int bits = 1 << (level - 1);
TreeNode tmp = root;
while (tmp != null && bits > 0) {
if ((bits & pos) == 0) {
tmp = tmp.left;
} else {
tmp = tmp.right;
}
bits >>= 1;
}
return tmp != null;
}
总结:
算层数的时间复杂度:O(h)
二分查找的次数为 O(h),每次查找的时间复杂度为 O(h),合起来的时间复杂度为 O(h²)
时间复杂度:O(h²) + O(h),根据主定理,时间复杂度为:O(h²),即 O(log²n)
空间复杂度:O(1)
在 n 足够大时,时间复杂度要远低于 dfs 的 O(n)。