[LeetCode]222. Count Complete Tree Nodes 左右分区算法解析

题目描述

LeetCode原题链接:222. Count Complete Tree Nodes

Given the root of a complete binary tree, return the number of the nodes in the tree.

According to Wikipedia, every level, except possibly the last, is completely filled in a complete binary tree, and all nodes in the last level are as far left as possible. It can have between 1 and 2h nodes inclusive at the last level h.

Design an algorithm that runs in less than O(n) time complexity.

 

Example 1:

Input: root = [1,2,3,4,5,6]
Output: 6

Example 2:

Input: root = []
Output: 0

Example 3:

Input: root = [1]
Output: 1

 

Constraints:

  • The number of nodes in the tree is in the range [0, 5 * 104].
  • 0 <= Node.val <= 5 * 104
  • The tree is guaranteed to be complete.

题干分析

根据 Complet Binary Tree 的性质:

  1. 从根节点到倒数第二层是满二叉树;
  2. 最后一层(第n层)节点可以不足 2个且这些节点从左到右依次填充。

我们可以发现,对于任意一棵完全二叉树上的任意一个节点,其左右孩子节点必然是满二叉树or完全二叉树。比如下面这棵满二叉树,对于节点A,它的左孩子就是一棵满二叉树,右孩子是一棵完全二叉树;而对于B节点,其左右孩子都是满二叉树;而对于c节点,其左孩子是一棵完全二叉树,右孩子则是满二叉树。

而我们知道,对于一棵高度是 h 的满二叉树,它共拥有 2h - 1 个节点。那我们就可以根据这个公式来计算题目要求的完全二叉树节点总和:不断分割出满二叉树,根据公式求出每一个小满二叉树的节点树然后累加。

先来写一个求高度函数

我们细分的每个子树可能是满二叉树,也可能是完全二叉树,它的高度应该以最左面节点所在层级为准(Complet Binary Tree 性质二)。因此,我们只需要用一个指针来深度遍历到最左下方的节点就可以了。可以用递归来解,每一轮高度都加1,如果当前节点是NULL则返回-1。

1 int getHeight(TreeNode* root) {
2     return root ? 1 + getHeight(root -> left) : -1;
3 }

这样计算出来的结果就是只有一层的二叉树对应高度是0,二层的二叉树对应高度是1,三层的二叉树对应高度是2......

分割原始二叉树

这个解法的关键是如何分割原始的二叉树 —— 根据前面的分析, 我们可以根据左右子树的高度差来判断哪边是满二叉树。具体步骤我们以上图的那棵树来讲解。设起始位置为根节点:

step1: 计算得到从根节点到最左下方节点的高度为5

step2: 计算根节点的右孩子的高度为2

step3: 计算高度差,可以看出heightL是包含根节点的高度,heightR不包含根节点,像上面这种情况,如果 heightR = heightL - 1,那么根节点的左孩子一定是一棵满二叉树(最后一层L节点前的位置都是排满的),此时可以直接计算出 根节点 + 左孩子 的节点和为 1 + (23 - 1) = 23,即 2heightL。此时,我们已经完成了一次分割,然后将指针从A移到其右孩子,进入下一轮循环。

step4: 新一轮循环,从C节点开始,重复步骤1、2,计算高度差时发现相差为2,也就是第二种情况,此时左孩子是完全二叉树,右孩子是满二叉树:

我们将根节点(这里是C节点)和其右孩子分割出来,计算其节点和:1 + (21 - 1) = 21,即2heightL - 1,然后和上一轮结果累加。指针移向左孩子。

step5: 新一轮循环,重复上面的步骤,继续累加分割的子树的节点和,最终当指针为NULL时表明分割原始二叉树完毕,跳出循环,得到最终节点总数。

 

上述过程中的heightL并不需要每一轮循环都调用getHeight函数。我们每次移动指针都是从根节点移到其左孩子或右孩子,相当于向下移动了一层。因此,只要在每轮循环末尾将上一轮计算的heightL减1就可以了;也就是说,只需要在最初计算一次heightL就足够了!这个过程可以用while循环来实现:

 1 int countNodes(TreeNode* root) {
 2     int h = getHeight(root), count = 0;
 3     while(root) {
 4         // 情况一:左孩子是满二叉树(右孩子可能是完全二叉树,也可能是满二叉树)
 5         if(getHeight(root -> right) == h - 1) {
 6             count += 1 << h; // 根节点 + 左孩子
 7             root = root -> right;
 8         }
 9         // 情况二:左孩子是完全二叉树(右孩子是满二叉树,也可能不存在)
10         else {
11             count += 1 << (h - 1); // 根节点 + 右孩子
12             root = root -> left;
13         }
14         h--;
15     }
16     return count;
17 }

完整代码示例(c++)

 1 /**
 2  * Definition for a binary tree node.
 3  * struct TreeNode {
 4  *     int val;
 5  *     TreeNode *left;
 6  *     TreeNode *right;
 7  *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 8  *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 9  *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
10  * };
11  */
12 class Solution {
13 public:
14     int countNodes(TreeNode* root) {
15         int h = getHeight(root), count = 0;
16         while(root) {
17             if(getHeight(root -> right) == h - 1) {
18                 count += 1 << h;
19                 root = root -> right;
20             }
21             else {
22                 count += 1 << (h - 1);
23                 root = root -> left;
24             }
25             h--;
26         }
27         return count;
28     }
29     int getHeight(TreeNode* root) {
30         return root ? 1 + getHeight(root -> left) : -1;
31     }
32 };

 

posted @ 2021-07-27 22:57  夭夭夭夭夭桃子  阅读(83)  评论(0)    收藏  举报