Leetcode 每日一题:Count Complete Tree Nodes

news2024/11/17 5:54:48

写在前面:

今天带来一道 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.coloradmin.cn/o/2133544.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

经典负载调制平衡放大器(LMBA)设计-从理论到ADS仿真

经典负载调制平衡放大器&#xff08;LMBA&#xff09;设计-从理论到ADS仿真 ADS工程下载&#xff1a;经典负载调制平衡放大器&#xff08;LMBA&#xff09;设计-从理论到ADS仿真-ADS工程 参考论文: An Efficient Broadband Reconfigurable Power Amplifier Using Active Load…

华为 HCIP 认证费用和报名资格

在当今竞争激烈的信息技术领域&#xff0c;华为 HCIP认证备受关注。它不仅能提升个人的技术实力与职业竞争力&#xff0c;也为企业选拔优秀人才提供了重要依据。以下将详细介绍华为 HCIP 认证的费用和报名资格。 一、HCIP 认证费用 华为HCIP认证的费用主要由考试费和培训费构成…

似然函数与先验概率、后验概率的关系

似然函数、先验概率、后验概率这三个概念是贝叶斯统计中的核心概念&#xff0c;它们共同描述了如何根据已有数据更新我们对某个事件或参数的认识。下面用简单的语言解释这三个概念&#xff0c;并描述它们之间的关系。 1. 先验概率&#xff08;Prior Probability&#xff09; …

Debian11.9镜像基于jre1.8的Dockerfile

Debian11.9基于jre1.8的Dockerfile编写 # 使用Debian 11.9作为基础镜像 FROM debian:11.9 # 维护者信息&#xff08;建议使用LABEL而不是MAINTAINER&#xff0c;因为MAINTAINER已被弃用&#xff09; LABEL maintainer"caibingsen" # 创建一个目录来存放jre …

vue中v-bind和v-model的区别和应用

1.区别 v-bind&#xff1a; vue2中&#xff0c;v-bind是单向数据绑定&#xff0c;用于动态绑定HTML属性和组件属性&#xff0c;只能将vue实例中的数据同步到HTML元素上&#xff0c;实现数据的动态更新和响应式渲染。v-bind的简写形式使用冒号前缀&#xff08;&#xff1a;&am…

VSCode好用的插件推荐

1. Chinese 将vscode翻译成简体中文 2. ESLint 自动检查规范 3. Prettier - Code formatter 可以自动调整代码的缩进、换行和空格&#xff0c;确保代码风格统一。通过配置&#xff0c;Prettier可以在保存文件时自动格式化代码 https://juejin.cn/post/74025724757198274…

【时间盒子】-【7.标题菜单栏】自定义任务页面顶部的标题菜单栏组件

Tips&#xff1a; media媒体资源的使用&#xff1b; float.json、color.json资源文件的使用&#xff1b; 组件属性的定义。 预览效果&#xff1a; 一、创建组件文件 右击component目录 >> 新建 >> ArkTS File&#xff0c;文件命名为TitleContainer.ets。 Prev…

JZ2440开发板——S3C2440的时钟体系

参考博客 &#xff08;1&#xff09;S3C2440-裸机篇-05 | S3C2440时钟体系详解&#xff08;FCLK、PCLK、HCLK&#xff09; 一、三种时钟&#xff08;FCLK、HCLK、PCLK&#xff09; 如下图所示&#xff0c;S3C2440的时钟控制逻辑&#xff0c;给整个芯片提供三种时钟&#xff1…

通过防火墙分段增强网络安全

什么是网络分段‌ 随着组织规模的扩大&#xff0c;管理一个不断扩大的网络成为一件棘手的事情&#xff0c;同时确保安全性、合规性、性能和不间断的运行可能是一项艰巨的任务。为了克服这一挑战&#xff0c;网络管理员部署了网络分段&#xff0c;这是一种将网络划分为更小且易…

QT::QComboBox自定义左击事件信号

因为QComboBox没有自定义的clink信号&#xff0c;所以自己新建一个MyComBox类继承QComboBox&#xff0c;并且添加自定义的左击信号&#xff0c;以及使用该信号连接一个槽函数 mycombobox.h #ifndef MYCOMBOBOX_H #define MYCOMBOBOX_H#include <QComboBox> #include &l…

使用程序集解析的方式内嵌dll到exe中

选择一个项目&#xff08;demo3&#xff09;&#xff0c;来进行内嵌。正常dll文件是可以在Bin–Debug里面看到的。 在Program里面添加内容 Program.cs里的全部代码 using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using Syste…

【面试八股总结】Redis持久化

Redis 实现了数据持久化的机制&#xff0c;这个机制会把数据存储到磁盘&#xff0c;这样在 Redis 重启就能够从磁盘中恢复原有的数据。 Redis 共有三种数据持久化的⽅式&#xff1a; AOF 日志&#xff1a;每执行一条写操作命令&#xff0c;就把该命令以追加的方式写入到⼀个文…

7.5图像缩放

实验原理 在OpenCV&#xff08;Open Source Computer Vision Library&#xff09;中&#xff0c;resize函数用于调整图像的尺寸。这个函数非常有用&#xff0c;尤其是在进行图像预处理时&#xff0c;比如在图像识别或机器学习任务中需要统一输入图像的大小。 下面是基于C的re…

Qt与Udp

(1)绑定端口 (2)广播 用udp实现广播通信_udp广播-CSDN博客 数据的发送是面向整个子网的&#xff0c;任何一台在子网中的计算机都可以接收到相同的数据。 如果一台机器希望向其他N台机器发送信息&#xff0c;这时候可以使用UDP的广播。 --------------- 广播地址&#xff1…

大数据-133 - ClickHouse 基础概述 全面了解

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff08;已更完&#xff09;HDFS&#xff08;已更完&#xff09;MapReduce&#xff08;已更完&am…

如何编译OpenHarmony SDK API

往期知识点记录&#xff1a; 鸿蒙&#xff08;HarmonyOS&#xff09;应用层开发&#xff08;北向&#xff09;知识点汇总 startup子系统之syspara_lite系统属性部件 &#xff08;1&#xff09; startup子系统之syspara_lite系统属性部件 &#xff08;2&#xff09; startup子系…

【数据集】城市不透水面数据集CLUD-Urban

城市不透水面数据集CLUD-Urban 数据概述数据下载参考 数据概述 1、论文-ESSD-A 30 m resolution dataset of China’s urban impervious surface area and green space, 2000–2018 空间分辨率&#xff1a;30 m 数据下载 数据下载&#xff1a;A 30-meter resolution data…

Grafana面板-linux主机详情(使用标签过滤主机监控)

1. 采集器添加labels标签区分业务项目 targets添加labels &#xff08;模板中使用的project标签&#xff09; … targets: [‘xxxx:9100’] labels: project: app2targets: [‘xxxx:9100’] labels: project: app1 … 2. grafana面板套用 21902 模板 演示

航空航司reese84逆向

reese84逆向 Reese84 是一种用于保护网站防止自动化爬虫抓取的防护机制&#xff0c;尤其是在航空公司网站等需要严格保护数据的平台上广泛使用。这种机制通过复杂的指纹识别和行为分析技术来检测和阻止非人类的互动。例如&#xff0c;Reese84 可以通过分析访问者的浏览器指纹、…

软件开发人员的真实面

我相信我们都看过视频上那些名为“软件工程师的一天”的视频。这些视频通常只展示一些日常任务&#xff0c;比如吃饭、打字和参加会议。我对这些视频未能展示软件开发工作的真实内容感到失望。这些内容往往只关注表面活动&#xff0c;却忽略了工作中的思维挑战和解决问题的部分…