完全二叉树(222)

在上一篇中,我们学习了解了平衡二叉树,并且利用DFS进行了验证。在本节中,我们将继续学习完全二叉树的相关内容。首先了解一下什么是完全二叉树。

01、完全二叉树

完全二叉树由满二叉树引出,先来了解一下什么是满二叉树:


如果二叉树中除了叶子结点,每个结点的度都为 2,则此二叉树称为满二叉树。(二叉树的度代表某个结点的孩子或者说直接后继的个数。 对于二叉树而言,1度是只有一个孩子或者说单子树,2度是有两个孩子或者说左右子树都有。)


比如下面这颗:

img

那什么又是完全二叉树呢:


如果二叉树中除去最后一层节点为满二叉树,且最后一层的结点依次从左到右分布,则此二叉树被称为完全二叉树


比如下面这颗:

img

而这颗就不是:

img

熟悉了概念,我们还是一起来看题目吧。

02、题目分析

第222题:完全二叉树的节点个数
给出一个完全二叉树,求出该树的节点个数。

说明:

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


示例:

  1. 输入:
  2. 1
  3. / \
  4. 2 3
  5. / \ /
  6. 4 5 6
  7. 输出: 6

03、递归求解

首先分析题目,我们很容易可以想到通过递归,来求解节点个数。

  1. func countNodes(root *TreeNode) int {
  2. if root != nil {
  3. return 1 + countNodes(root.Right) + countNodes(root.Left)
  4. }
  5. return 1 + countNodes(root.Right) + countNodes(root.Left)
  6. }

执行结果:

img

但是很明显,出题者肯定不是要这种答案。因为这种答案和完全二叉树一毛钱关系都没有。所以我们继续思考。

04、经典解法

由于题中已经告诉我们这是一颗完全二叉树,我们又已知了完全二叉树除了最后一层,其他层都是满的,并且最后一层的节点全部靠向了左边。那我们可以想到,可以将该完全二叉树可以分割成若干满二叉树和完全二叉树满二叉树直接根据层高h计算出节点为2^h-1,然后继续计算子树中完全二叉树节点。那如何分割成若干满二叉树和完全二叉树呢?对任意一个子树,遍历其左子树层高left,右子树层高right,相等左子树则是满二叉树,否则右子树是满二叉树。这里可能不容易理解,我们看图。


假如我们有树如下:

img

我们看到根节点的左右子树高度都为3,那么说明左子树是一颗满二叉树。因为节点已经填充到右子树了,左子树必定已经填满了。所以左子树的节点总数我们可以直接得到,是2^left - 1,加上当前这个root节点,则正好是2^3,即 8。然后只需要再对右子树进行递归统计即可。

img

那假如我们的树是这样:

img

我们看到左子树高度为3,右子树高度为2。说明此时最后一层不满,但倒数第二层已经满了,可以直接得到右子树的节点个数。同理,右子树节点+root节点,总数为2^right,即2^2。再对左子树进行递归查找。

img

根据分析,得出代码:

  1. class Solution {
  2. public int countNodes(TreeNode root) {
  3. if (root == null) {
  4. return 0;
  5. }
  6. int left = countLevel(root.left);
  7. int right = countLevel(root.right);
  8. if (left == right) {
  9. return countNodes(root.right) + (1 << left);
  10. } else {
  11. return countNodes(root.left) + (1 << right);
  12. }
  13. }
  14. private int countLevel(TreeNode root) {
  15. int level = 0;
  16. while (root != null) {
  17. level++;
  18. root = root.left;
  19. }
  20. return level;
  21. }
  22. }

运行结果:

img