当前位置: 首页 > article >正文

Leetcode 每日一题:Count Complete Tree Nodes

写在前面:

今天带来一道 Leetcde Easy 的题,但别觉得我在水帖,这道题目在 Google 的面试题中甚至可以升级到 Leetcode medium to hard 的级别,而今天我要带来的正是他的高阶要求,怎么样利用 Complete Binary Tree 的特性对 Tree traversal 进行优化,并且在 Runtime 的限制内解决问题,就让我们一起来看看这道题吧!

题目介绍:

题目信息:

  • 题目链接:https://leetcode.com/problems/count-complete-tree-nodes/description/
  • 题目类型:Binary Tree,Recursion,TreeTraversal,Math
  • 题目难度:Easy (但我带来的这道升级难度应该为 Medium 以上)
  • 题目来源:Google 高频面试题

题目介绍:

  • 给定一个 Binary Tree,并且这个 Tree 是 Complete Binary Tree
    • Complete Binary Tree:
    • 除了最后一层以外,所有上层的每一层都没有空节点,均有左右两个 children
    • 最后一层中,从左到右之间没有空隙空节点
  • 要求找出这个给定 tree 的所有节点个数并返回
  • 要求 Runtime 必须在 O(n) 以内

题目想法:

BruteForce:

这道题之所以是 Easy 的原因就是在于它本身不难,只需要用最基本的 Recursion 将整个 tree 遍历一遍就行,每经过一个非空节点增加一个个数,最后返回总个数。

这种方法的代码我就不详细说了,非常简单。但是 Runtime 上需要遍历每一个节点,所以复杂度为 O(n),并不能满足 Google 面试对这道题目的要求,并且也没有利用 Complete Tree 的性质,所以我们优化的方向主要探索如何利用 Complete Tree

Complete Tree 除了最后一层:

因为 Complete Tree 除了最后一层以外,其他层都是保证所有节点都是塞满的,所以我们的问题就变成了:前面所有节点 + 最后一层有几个节点

而针对一个 满 Binary Tree 的节点,我们只需要知道他的深度 d,再利用公式:

num = pow(2, d) - 1

 即可求出除了最后一层以外的所有点。

最后一层遍历,利用 binary search

因为最后一层的点一定是从左到右,中间没有空隙的。我们可以通过 binary tree 的思想逼近找到最右边的那个存在的点,这样就可以确定最后一层一共有几个节点了

根据公式,最后一层最多一共有: 2^d 个节点,而第一个节点我们已经确定是有了(complete tree),所以我们只需要将剩下的 2^d - 1 个节点编号成为 1, 2, ....., 2^d - 1,对这些点做一个binary search。

最后一层 Binary Tree 思路:当目标点不存在时,我们知道右边绝对不会再有节点了,所以我们直接看左半边就行。而当目标点存在时,我们知道左半边一定都有,我们只需要看右半边就可以,这样依次逼近,当 左右 节点相等时,我们就能找到这个最边缘的点,也就可以知道最后一层一共有几个了

而对于每一次的 目标点是否存在,我们又可以利用一次 binary search,因为  binary tree 和 binary search 的结构是相似的:

找寻目标点的 Binary Search 思路:我们最后一层所有点编为 0 -> 2^d - 1,然后展开 binary tree,遍历 d 次,d 为深度

每次遍历时,如果目标点小于 最后一点的 target,我们知道他肯定在 tree 的右半边,我们将root 节点变为 root-> right,反之我们去 root->left。如果我们遍历到了一个空节点,我们直接返回这个点吧不存在

如果我们遍历 d 次后还在实体点,则说明这个点存在,返回 true

而最后的结果也将是:

2^d + 1 + 最后一层点数 

题目代码:

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    int getDepth(TreeNode* root){
        //stop at the last level
        int d = 0;
        while(root->left){
            //since it is a complete tree, every level shall have the leftmost
            root = root->left; 
            d += 1;
        }
        return d;
    }
    
    bool exists(int target, TreeNode* root, int depth){
        //the last level have total 2^d node, denote from 0 to 2^d - 1
        int left = 0, right = pow(2, depth) - 1;
        for(int i = 0; i < depth; i++){
            int pivot = (left + right) / 2;
            //use the binary tree property, if the target smaller than pivot, then it must be
            //in the left subtree of the tree, and vice versa
            if(target <= pivot){
                root = root->left;
                right = pivot;
            }else{
                root = root->right;
                left = pivot + 1;
            }
            
            if(root == nullptr){
                break;
            }
        }
        
        //at the very last, if the node is not nullptr, then we shall say it is exists. 
        return (root != nullptr);
    }
    
    
    int countNodes(TreeNode* root) {
        if(!root)
            return 0;
        //find the depth of the tree, then calculate the d-1 complete nodes:
        int d = getDepth(root);
        int res = pow(2, d) - 1; 
        
        if(d == 0)
            return 1;
        
        //use binary search to check how many nodes exist in the last level
        //actually use the binary tree to target the right-most node at the last level
        int left = 1, right = pow(2, d) - 1;
        while(left <= right){
            int pivot = (left + right) / 2;
            //if the pivot exist, then we are confident to say all the left behind is exist
            if(exists(pivot, root, d)){
                left = pivot + 1;
            }else{
                //since complete tree, if pivot don't exist, then every node at the right
                //side won't exist
                right = pivot - 1;
            }
        }
        
        return res + left; 
    }
};
  • Runtime:O(log^2 N) + O(log^N) = O(log^N)
  • Space: O(1)
  • 满足小于 O(N) 的需求,而且会比 O(N) 快很多

http://www.kler.cn/news/303765.html

相关文章:

  • webpack打包原理
  • QT 串口上位机读卡显示
  • DMA与AXI DMA ip
  • PMP–一、二、三模–分类–14.敏捷–技巧–敏捷团队通才型专家
  • Golang数据流处理:掌握Reader和Writer接口的技巧
  • 信息系统容灾等级
  • 【docker】docker network 网络
  • C++数据结构
  • STM32 HAL freertos零基础(九)任务通知
  • 【Python深度学习】逆强化学习(IRL):通俗揭开学习背后的奥秘
  • vue devtools的使用
  • 外包干了3天,技术退步明显.......
  • Apache DataFusion查询引擎简介
  • 0to1使用Redis实现“登录验证”次数限制
  • 【面试题】什么是代理以及如何实现代理
  • shader 案例学习笔记之将坐标系分成4个象限
  • JVM面试真题总结(八)
  • 浅谈WebApi
  • 低压电抗器与电容器安装距离
  • 爆改YOLOv8|利用yolov9的ADown改进卷积Conv-轻量化
  • MySQL--数据库基础
  • 【iOS】——应用启动流程
  • 【GBase 8c V5_3.0.0 分布式部署(单机安装)】
  • 软件开发人员的真实面
  • TinyRedis项目复盘
  • 【动态规划】子序列问题二(数组中不连续的一段)
  • 系统资源智能管理:zTasker软件的监控与优化
  • 小需求:(vue2) 判断el-table某一行某一格里面是否包含‘百度‘两个字,如果包含,点击‘百度‘两个字跳转到‘百度‘页面,并给‘百度‘两个字加蓝色颜色
  • HTML+CSS - 网页布局之网格布局
  • IO多路复用,服务器,广播与组播