完全二叉树的节点个数

标签: 位运算 二分查找 二叉树

难度: Easy

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

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

 

示例 1:

输入:root = [1,2,3,4,5,6]
输出:6

示例 2:

输入:root = []
输出:0

示例 3:

输入:root = [1]
输出:1

 

提示:

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

 

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

Submission

运行时间: 80 ms

内存: 21.7 MB

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def countNodes(self, root: TreeNode) -> int:
        def get_height(root):
            if root is None:
                return 0
            return get_height(root.left) + 1

        height = get_height(root)
        if height <= 1:
            return height
        if get_height(root.right) == height - 2:
            return (1 << (height - 2)) + self.countNodes(root.left)
        else:
            return (1 << (height - 1)) + self.countNodes(root.right)

Explain

该题解采用了递归的思路,利用完全二叉树的性质来优化计算节点个数的过程。首先通过 get_height 函数递归计算完全二叉树的高度。如果高度小于等于1,直接返回高度作为节点个数。否则,通过判断右子树的高度是否等于总高度减2,如果是,说明左子树是一个满二叉树,可以直接计算左子树的节点数,再加上右子树递归计算的节点数;如果右子树高度不等于总高度减2,说明右子树是一个满二叉树,可以直接计算右子树的节点数,再加上左子树递归计算的节点数。通过这样的方式,可以避免遍历整棵树,从而降低时间复杂度。

时间复杂度: O((log n)^2)

空间复杂度: O(log n)

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def countNodes(self, root: TreeNode) -> int:
        def get_height(root):
            if root is None:
                return 0
            return get_height(root.left) + 1

        height = get_height(root)
        if height <= 1:
            return height
        # 判断右子树的高度是否等于总高度减2
        if get_height(root.right) == height - 2:
            # 如果是,左子树是满二叉树,计算左子树的节点数,再加上右子树递归计算的节点数
            return (1 << (height - 2)) + self.countNodes(root.left)
        else:
            # 如果不是,右子树是满二叉树,计算右子树的节点数,再加上左子树递归计算的节点数
            return (1 << (height - 1)) + self.countNodes(root.right)

Explore

在完全二叉树中,如果一个节点的左子树的高度等于右子树的高度,则该节点及其所有子树构成一个满二叉树,所以可以通过只计算左子树高度快速确定树的高度。这是因为在完全二叉树中,节点是从左到右依次填充的,左子树总是比右子树先填满或同时填满。因此,左子树的高度可以作为判断整棵树高度的关键指标。

在`get_height`函数中,基准情况是当节点不存在(即节点为`None`)时返回0。这表示树是空的,因此没有高度。这是递归计算树高度的终止条件,保证了每次递归能够在遇到叶节点的子节点时停止。

这种条件判断对于快速确定树的结构非常关键。如果右子树的高度等于总高度减2,则意味着左子树是一个满二叉树且与最底层除最右侧外完全填满,而右子树可能还未完全填满。这样可以利用满二叉树的节点数性质直接计算左子树的节点数,而不需要逐节点遍历,从而提高效率。如果不符合这个条件,则右子树是一个满二叉树,而左子树未完全填满,同样可以快速计算右子树的节点数并递归计算左子树。

在二叉树中,满二叉树的节点数可以通过计算`2^k - 1`得到,其中`k`是树的高度。在位操作中,`1 << n`表示`2^n`。因此,`(1 << (height - 2))`计算的是高度为`height-1`的满二叉树的节点数,而`(1 << (height - 1))`计算的是高度为`height`的满二叉树的节点数。这些计算利用位移操作提高计算效率,并直接得到对应高度的满二叉树的节点总数。