• [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 };
  • 相关阅读:
    sql server中使用链接服务器访问oracle数据库
    biztalk中使用信封(Envelope)消息
    EMS SQL Manager 2007 for MySQL发布
    MySQL Connector/NET
    Silverlight相关资源
    ADO.NET嵌套数据绑定
    收到网上订得书了,开始充电...
    几个.net下的ajax框架
    Visual Studio 2008 Beta 2 初步体验
    .Net Remoting常用部署结构
  • 原文地址:https://www.cnblogs.com/barryyeee/p/15068138.html
Copyright © 2020-2023  润新知