LeetCode 654.最大二叉树

news2024/9/23 5:35:02

给定一个不重复的整数数组 nums 。 最大二叉树 可以用下面的算法从 nums 递归地构建:

创建一个根节点,其值为 nums 中的最大值。
递归地在最大值 左边 的 子数组前缀上 构建左子树。
递归地在最大值 右边 的 子数组后缀上 构建右子树。
返回 nums 构建的 最大二叉树 。

示例 1:
在这里插入图片描述

输入:nums = [3,2,1,6,0,5]
输出:[6,3,5,null,2,0,null,null,1]
解释:递归调用如下所示:

  • [3,2,1,6,0,5] 中的最大值是 6 ,左边部分是 [3,2,1] ,右边部分是 [0,5] 。
    • [3,2,1] 中的最大值是 3 ,左边部分是 [] ,右边部分是 [2,1] 。
      • 空数组,无子节点。
      • [2,1] 中的最大值是 2 ,左边部分是 [] ,右边部分是 [1] 。
        • 空数组,无子节点。
        • 只有一个元素,所以子节点是一个值为 1 的节点。
    • [0,5] 中的最大值是 5 ,左边部分是 [0] ,右边部分是 [] 。
      • 只有一个元素,所以子节点是一个值为 0 的节点。
      • 空数组,无子节点。
        示例 2:
        在这里插入图片描述

输入:nums = [3,2,1]
输出:[3,null,2,null,1]

提示:

1 <= nums.length <= 1000
0 <= nums[i] <= 1000
nums 中的所有整数 互不相同

法一:递归构建:

/**
 * 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:
    TreeNode* constructMaximumBinaryTree(vector<int>& nums) {
        return build(nums, 0, nums.size() - 1);
    }

private:
    TreeNode *build(vector<int> &nums, int begin, int end)
    {
        if (begin > end)
        {
            return nullptr;
        }

        int max = -1;
        int maxIndex = -1;
        for (int i = begin; i <= end; ++i)
        {
            if (nums[i] > max)
            {
                max = nums[i];
                maxIndex = i;
            }
        }

        TreeNode *node = new TreeNode(max);
        node->left = build(nums, begin, maxIndex - 1);
        node->right = build(nums, maxIndex + 1, end);

        return node;
    }
};

如果nums的长度为n,此算法时间复杂度为O(n 2 ^2 2),空间复杂度为O(1)。

法二:单调栈,单调栈用于在线性时间内寻找数组中每个元素左边第一个大(小)于该值的值,或右边第一个大(小)于该值的值。简单介绍一下单调栈,我们遍历nums中的每个元素,每遍历到一个元素,如果栈顶的元素小于当前遍历到的元素,就做出栈操作,直到栈顶元素值大于当前遍历到的元素,或栈为空,这些出栈元素右边第一个大于它们的值就是当前遍历到的元素,做完出栈操作后,栈顶元素就是当前元素的左边第一个大于当前元素的值,然后再把当前遍历到的元素入栈:

/**
 * 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:
    TreeNode* constructMaximumBinaryTree(vector<int>& nums) {
        stack<int> s;
        vector<TreeNode *> nodeArr(nums.size(), nullptr);
        TreeNode *root = nullptr;
        for (int i = 0; i < nums.size(); ++i)
        {
            nodeArr[i] = new TreeNode(nums[i]);
            while (!s.empty() && nums[s.top()] < nums[i])
            {
                nodeArr[i]->left = nodeArr[s.top()];
                s.pop();
            }

            if (!s.empty())
            {
                nodeArr[s.top()]->right = nodeArr[i];
            }

            s.push(i);

            if (s.size() == 1)
            {
                root = nodeArr[i];
            }
        }

        return root;
    }
};

如果nums的长度为n,此算法时间复杂度为O(n),空间复杂度为O(n)。

法三:线段树,线段树可以logn的效率查找某区间内的最大值,将其与法一中找区间最大值的部分替换,可将找极值的时间复杂度降为logn。线段树的核心在于把数组的每一段的最大值以树来存储,如找数组3、2、1、6、0、5的极值,我们递归地处理它,第一步将其分为两部分,分别为3、2、1和6、0、5,然后极值为这两部分极值中较大的那个,继续递归处理,直到只有单个元素,此时我们就可以知道当前区间的极值了,因为只有一个元素,极值就是它本身,然后递归向上返回,即可求得每一段的极值:

/**
 * 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:
    TreeNode* constructMaximumBinaryTree(vector<int>& nums) {
        // 线段树需要开4倍内存
        nodeArr.resize(nums.size() * 4 , nullptr);

        buildSegmentTree(1, nums.size(), 1, nums);

        return buildAns(1, nums.size());
    }

private:
    TreeNode *buildAns(int left, int right)
    {
        if (left > right)
        {
            return nullptr;
        }

        pair<int, int> max = search(left, right, 1);
        int maxIndex = max.first;
        int maxValue = max.second;

        TreeNode *node = new TreeNode(maxValue);
        node->left = buildAns(left, maxIndex - 1);
        node->right = buildAns(maxIndex + 1, right);

        return node;
    }

    class Node
    {
    public:
        Node(int left, int right, int maxValue, int maxIndex) 
            : left(left), 
              right(right), 
              maxValue(maxValue),
              maxIndex(maxIndex)
        {

        }

        int left;
        int right;
        int maxValue;
        int maxIndex;
    };

    vector<Node *> nodeArr;

    void buildSegmentTree(int left, int right, int x, vector<int> &nums)
    {
        if (left > right)
        {
            return;
        }

        // 如果是叶子结点
        if (left == right)
        {
            nodeArr[x] = new Node(left, right, nums[left - 1], left);
            return;
        }

        int mid = left + (right - left) / 2;
        buildSegmentTree(left, mid, 2 * x, nums);
        buildSegmentTree(mid + 1, right, 2 * x + 1, nums);
        
        if (nodeArr[2 * x]->maxValue > nodeArr[2 * x + 1]->maxValue)
        {
            nodeArr[x] = new Node(left, right, nodeArr[2 * x]->maxValue, nodeArr[2 * x]->maxIndex);
        }
        else
        {
            nodeArr[x] = new Node(left, right, nodeArr[2 * x + 1]->maxValue, nodeArr[2 * x + 1]->maxIndex);
        }
    }

    pair<int, int> search(int left, int right, int x)
    {
        if (left == nodeArr[x]->left && right == nodeArr[x]->right)
        {
            return {nodeArr[x]->maxIndex, nodeArr[x]->maxValue};
        }

        int mid = (nodeArr[x]->left + nodeArr[x]->right) / 2;

        // 如果要查找的范围在当前节点表示范围中点的左边(包括mid,因为建线段树时mid属于左边子树)
        // 说明要查找的范围在左子树上
        // 之所以与mid做对比,是因为子树的每个节点的范围就是按mid分的
        if (right <= mid)
        {
            return search(left, right, 2 * x);
        }

        // 如果要查找的范围在当前节点表示范围中点的右边,说明要查找的范围在右子树上
        // 此处要用>而非>=,因为mid属于右子树,left只有完全属于右子树时,才只在右子树上查
        if (left > mid)
        {
            return search(left, right, 2 * x + 1);
        }
 
        // 如果跨mid,就从两个子树中找
        pair<int, int> leftMax = search(left, mid, 2 * x);
        pair<int, int> rightMax = search(mid + 1, right, 2 * x + 1);

        if (leftMax.second > rightMax.second)
        {
            return leftMax;
        }
        else
        {
            return rightMax;
        }
    }
};

如果nums的长度为n,此算法时间复杂度为O(nlogn),空间复杂度为O(n)。

法四:ST表,类似法三,ST表能以O(1)的时间查找某一段的极值。ST表的建表用时为O(nlogn),而线段树的建树用时为O(n),但查询时,ST表比线段树快得多。线段树可以动态更改节点值,而ST表不支持,因此ST表更适用于值不会被修改的情况。简单介绍一下ST表,它是一个二维数组,a[i][j]表示从i开始的2的j次方个元素的极值是多少,在查询时,我们把查询区间分为有重叠部分的两段,然后取两段的极值做对比。为什么要取两段?因为二维数组里存的是从要查询的区间开始的、长为2的幂的区间的极值,我们查询的区间长度很有可能不是2的幂,因此需要分为两部分,这两部分一定会有重叠,这也隐含着ST表适用于那种有重叠部分也不影响结果的问题,如极值、求最大公约数等:

/**
 * 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:
    TreeNode* constructMaximumBinaryTree(vector<int>& nums) {
        // 由于nums中元素个数最大为1000,因此2的10次方即可完全覆盖
        int exp = log(nums.size()) / log(2);
        vector<vector<pair<int, int>>> st(nums.size(), vector<pair<int, int>>(exp + 1, {-1, -1}));
        for (int i = 0; i < st.size(); ++i)
        {
            // 初始化,从i开始,长为2的0次方(即1)的极值为它自己
            st[i][0] = {nums[i], i};
        }

        // j最大为9,我们只会用到2的9次方的值,因为查找时我们会把区间分为两段
        for (int j = 1; j < st[0].size(); ++j)
        {
            // 循环条件部分保证了不会索引超出
            for (int i = 0; i + (1 << (j - 1)) < nums.size(); ++i)
            {
                // st[i][j - 1]是2的j-1次方个数字,为第一段
                // st[i + (1 << (j - 1))][j - 1]也是2的j-1次方个数字,从第一段的后面一个位置开始
                // 注意st[i + (1 << (j - 1))][j - 1]中的i + (1 << (j - 1))部分是循环条件
                st[i][j] = max(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]);
            }
        }

        return build(0, nums.size() - 1, nums, st);
    }

private:
    TreeNode *build(int left, int right, vector<int> &nums, vector<vector<pair<int, int>>> &st)
    {
        if (left > right)
        {
            return nullptr;
        }

        // 以2为底,范围内数字个数的对数,将结果再去掉小数,这是最大的两段长度
        int num = log(right - left + 1) / log(2);
        // 两段会有交叉,如果left为0,right为5,则num为2
        // 此时会取从0开始的4个数字的极值和从1开始的4个数字的极值两者更大值
        const pair<int, int> &target = max(st[left][num], st[right - (1 << num) + 1][num]);
        int maxValue = target.first;
        int maxIndex = target.second;

        TreeNode *node = new TreeNode(maxValue);

        node->left = build(left, maxIndex - 1, nums, st);
        node->right = build(maxIndex + 1, right, nums, st);

        return node;
    }
};

此算法时间复杂度为O(nlogn),用于建表;空间复杂度为O(nlogn)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1502906.html

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

相关文章

付强:基于注意力机制的听觉前端处理 | 嘉宾公布

一、智能家居与会议系统专题论坛 智能家居与会议系统专题论坛将于3月28日同期举办&#xff01; 智能会议系统它通过先进的技术手段&#xff0c;提高了会议效率&#xff0c;降低了沟通成本&#xff0c;提升了参会者的会议体验。对于现代企业、政府机构和学术界是不可或缺的。在这…

静态时序分析:SDC约束命令set_disable_timing详解

静态时序分析https://blog.csdn.net/weixin_45791458/category_12567571.html 目录 指定对象列表 指定源、目的引脚 指定恢复 简单使用 写在最后 上一章中&#xff0c;我们学习了如何使用set_case_analysis模式分析命令&#xff0c;它通过指定某个端口或引脚为固定值&…

国产硅片膜厚检测仪

硅片膜厚检测仪是半导体行业中一种至关重要的设备&#xff0c;用于精确测量硅片上薄膜的厚度。在半导体制造工艺中&#xff0c;薄膜厚度的控制对于保证器件性能和可靠性具有决定性的作用。因此&#xff0c;硅片膜厚检测仪的研发和应用对于推动半导体技术的发展具有重要意义。 一…

登录校验认证

会话技术 会话&#xff1a;用户打开浏览器&#xff0c;访问web服务器的资源&#xff0c;会话建立&#xff0c;直到有一方断开连接&#xff0c;会话结束。在一次会话中可以包含多次请求和响应。 会话跟踪&#xff1a; 一种维护浏览器状态的方法&#xff0c;服务器需要识别多次请…

Scalable Diffusion Models with Transformers(DiTs)论文阅读 -- 文生视频Sora模型基础结构DiT

nlpcver 忠于理想 ​关注他 106 人赞同了该文章 文章地址&#xff1a;Scalable Diffusion Models with Transformers 简介 文章提出使用Transformers替换扩散模型中U-Net主干网络&#xff0c;分析发现&#xff0c;这种Diffusion Transformers&#xff08;DiTs&#xff09…

市域社会治理现代化指挥中心项目方案

1.4.1专题分析应用建设要求 1.4.1.1总体要求 系统根据各专题实际业务需求提供详细的指标体系清单&#xff0c;同时应根据指标体系提供设计各专题应用的原型效果图&#xff1b;围绕党建引领、基层治理、城市管理、公共服务、公共安全多方面进行分析展示核心数据&#xff0c;体…

AI跟踪报道第32期-新加坡内哥谈技术-本周AI新闻:超越GPT4的Claude

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

删除网络连接不存在的网络驱动器

目录预览 一、问题描述二、解决方案三、参考链接 一、问题描述 虚拟机之前连接了主机&#xff0c; 二、解决方案 方法一&#xff1a;从文件资源管理器断开 Windows 10 上映射的网络驱动器&#xff0c; 在 Windows 10 上打开文件资源管理器。 从左窗格中单击此 PC 。 在“网络…

Linux系统架构----nginx的访问控制

nginx的访问控制 一、nginx基于授权的访问控制概述 Nginx与Apache一样&#xff0c;可以实现基于用户权限的访问控制&#xff0c;当客户端想要访问相应的网站或者目录时&#xff0c;要求用户输入用户名和密码&#xff0c;才能正常访问配置步骤生成用户密码认证文件 &#xff1…

若依/RuoYi-Vue使用docker-compose部署

系统需求 JDK > 1.8 MySQL > 5.7 Maven > 3.0 Node > 12 Redis > 3 思路 前端服务器 nginx 后端服务器代码打包 java、maven、node 数据库/缓存 mysql、redis 开始 创建目录ruoyi并进入 克隆若依代码 git clone RuoYi-Vue: &#x1f389; 基于Spring…

【数据分享】2013-2022年全国范围逐日SO2栅格数据

空气质量数据是在我们日常研究中经常使用的数据&#xff01;之前我们给大家分享了2013-2022年全国范围逐月SO2栅格数据和逐年SO2栅格数据&#xff08;均可查看之前的文章获悉详情&#xff09;。 本次我们给大家带来的是2013-2022年全国范围的逐日的SO2栅格数据&#xff0c;原始…

mq基础类设计

消息队列就是把阻塞队列这样的数据结构单独提取成一个程序独立进行部署。——>实现生产者消费者模型。 但是阻塞队列是在一个进程内部进行的&#xff1b; 消息队列是在进程与进程之间进行实现的&#xff0c; 解耦合&#xff1a;就是在分布式系统中&#xff0c;A服务器调用B…

RT-DETR优化改进:特征融合篇 | GELAN(广义高效层聚合网络)结构来自YOLOv9

🚀🚀🚀本文改进:使用GELAN改进架构引入到RT-DETR 🚀🚀🚀RT-DETR改进创新专栏:http://t.csdnimg.cn/vuQTz 🚀🚀🚀学姐带你学习YOLOv8,从入门到创新,轻轻松松搞定科研; 🚀🚀🚀RT-DETR模型创新优化,涨点技巧分享,科研小助手; 1.YOLOv9介绍 论…

在用Java写算法的时候如何加快读写速度

对于解决该方法我们一般如下操作&#xff0c;不需要知道为什么&#xff0c;有模板&#xff08;个人观点&#xff09; 使用BufferedReader代替Scanner&#xff1a;Scanner类在读取大量输入时性能较差&#xff0c;而BufferedReader具有更高的读取速度。可以使用BufferedReader的r…

JVM的工作流程

目录 1.JVM 简介 2.JVM 执行流程 3. JVM 运行时数据区 3.1 堆&#xff08;线程共享&#xff09; 3.3 本地方法栈&#xff08;线程私有&#xff09; 3.4 程序计数器&#xff08;线程私有&#xff09; 3.5 方法区&#xff08;线程共享&#xff09; 4.JVM 类加载 ① 类…

webUI自动化之元素及浏览器操作

一、元素定位方式 1、元素属性定位&#xff1a; 1 element driver.find_element_by_id(self, id)    该类方法已经过时&#xff0c;新的方法如下&#xff1a; element driver.find_element(By.ID, ID 值)        # 用元素的 ID 属性定位element driver.find_eleme…

云打印软件免费版在哪?云打印服务怎么使用?

随着新的一年的到来&#xff0c;很多同学们又开始准备着新一轮的学习冲刺了。在学习的旅途中&#xff0c;打印资料的需求必然伴随着每一个人&#xff0c;但是线下打印店价格贵、打印不方便、没时间去打印等多种因素总是制约着我们。在这种情况下&#xff0c;云打印软件和云打印…

Svg Flow Editor 原生svg流程图编辑器(一)

系列文章 Svg Flow Editor 原生svg流程图编辑器&#xff08;二&#xff09; 效果展示 项目概述 svg flow editor 是一款流程图编辑器&#xff0c;提供了一系列流程图交互、编辑所必需的功能&#xff0c;支持前端研发自定义开发各种逻辑编排场景&#xff0c;如流程图、ER 图、…

【论文笔记】Scalable Diffusion Models with State Space Backbone

原文链接&#xff1a;https://arxiv.org/abs/2402.05608 1. 引言 主干网络是扩散模型发展的关键方面&#xff0c;其中基于CNN的U-Net&#xff08;下采样-跳跃连接-上采样&#xff09;和基于Transformer的结构&#xff08;使用自注意力替换采样块&#xff09;是代表性的例子。…

使用R语言进行聚类分析

一、样本数据描述 城镇居民人均消费支出水平包括食品、衣着、居住、生活用品及服务、通信、文教娱乐、医疗保健和其他用品及服务支出这八项指标来描述。表中列出了2016年我国分地区的城镇居民的人均消费支出的原始数据&#xff0c;数据来源于2017年的《中国统计年鉴》&#xf…