LeetCode 654.最大二叉树

给定一个不重复的整数数组 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)。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Spring Security | Oauth2 /oauth/token自定义授权实现底层源码浅析与实现

Spring Security Oauth2 /oauth/token自定义授权源码分析实现过程&#xff0c;看了网上很多文章&#xff0c;分析和实现肯定存在不完整地方&#xff0c;可以在评论区指出交流。 1 /oauth/token入口 org.springframework.security.oauth2.provider.endpoint.TokenEndpoint Token…

java的参数传递机制(引用类型)

1.除了非引用类型的形参传递&#xff0c;还有引用类型的变量形参传递&#xff0c;但引用类型的形参变量传递与非引用类型是不同的&#xff01;&#xff01;&#xff01; public class MethodDemo2 {public static void main(String[] args) {int[] arr new int[]{10,20,30,9}…

MySQL入门到中级知识汇总2024

文章目录 1.揭开MySQL的神秘面纱2. SQL的基本命令实操3. 数据库的备份与恢复4. MySQL常用的数据类型&#xff08;列类型&#xff09;5. 数据类型之小数类型的使用6. 表的创建7. 表的修改8. mysql事务9. mysql表类型和存储引擎10. mysql的视图11. mysql的管理 1.揭开MySQL的神秘…

20.2 nginx

20.2 nginx 1. 学习目标2. 介绍2.1 正向代理2.2 反向代理2.3 动态静态资源分离2.4 nginx优缺点3. 安装3.1 Linux安装****************************************************************************************************************************************************…

Charles-抓包工具的使用

文章目录 Charles简介与安装Charles的简介Charles的安装Charles 安装证书 抓包在PC端抓取HTTPS请求在移动端进行抓包移动端配置Androd端配置iOS端配置 Charles使用小技巧&#xff1a; 模拟慢速网络 Charles简介与安装 Charles的简介 Charles 是在 PC 端常用的网络封包截取工具…

块设备驱动(1)-什么是块设备驱动?块设备驱动概念总结

1.块设备驱动概念 块设备驱动是针对存储设备&#xff0c;例如SD卡、EMMC、NAND FLASH、NOR FLSASH。 块设备驱动以块为单位进行访问、最小寻址单位是扇区、一个块中包含多个扇区、支持随机访问、带缓冲区&#xff0c;&#xff0c;当发生写入操作时&#xff0c;并不会立马操作硬…

GPU,一统天下

三十年前&#xff0c;CPU 和其他专用处理器几乎处理所有计算任务。那个时代的显卡有助于加快 Windows 和应用程序中 2D 形状的绘制速度&#xff0c;但没有其他用途。 快进到今天&#xff0c;GPU 已经成为业界最具主导地位的芯片之一。 但具有讽刺意味的是&#xff0c;图形芯片…

checking file system on C

1、win7系统 开机检查C盘&#xff0c;虽然可以ESC取消检查&#xff0c;每次操作很麻烦&#xff0c;且没有意思 2、注册表清空BootExecute数值数据 1&#xff09;打开注册表 WinR &#xff08;快捷键&#xff09;输入“regedit”&#xff0c;回车 2&#xff09;位置HKEY_LOCAL…

HTML5+CSS3+移动web——CSS基础

系列文章目录 HTML5CSS3移动web——HTML 基础-CSDN博客https://blog.csdn.net/ymxk2876721452/article/details/136070953?spm1001.2014.3001.5501HTML5CSS3移动web——列表、表格、表单-CSDN博客https://blog.csdn.net/ymxk2876721452/article/details/136221443?spm1001.2…

基于stm32的流水灯设计

1基于stm32的流水灯设计[proteus仿真] 速度检测系统这个题目算是课程设计和毕业设计中常见的题目了&#xff0c;本期是一个基于51单片机的自行车测速系统设计 需要的源文件和程序的小伙伴可以关注公众号【阿目分享嵌入式】&#xff0c;赞赏任意文章 2&#xffe5;&#xff0c…

考研经验|如何从考研失败中走出来?

对我来说&#xff0c;太丢人了 其实我在本科的时候在同学眼中&#xff0c;一直很优秀&#xff0c;每年奖学金必有我的&#xff0c;国家励志奖学金&#xff0c;国家奖学金&#xff0c;这种非常难拿的奖学金&#xff0c;我也拿过&#xff0c;本科期间学校有一个公费去新西兰留学的…

Haproxy集群

一、HAProxy介绍 HAProxy是法国开发者威利塔罗(Willy Tarreau)在2000年使用C语言开发的一个开源软件&#xff0c;是一款具备高并发(一万以上)、高性能的TCP和HTTP负载均衡器&#xff0c;支持基于cookie的持久性&#xff0c;自动故障切换&#xff0c;支持正则表达式及web状态统…

数据库--SQL语言-1

练习网站&#xff1a;自学SQL网 Select 查询语法复习 SELECT column, another_column, …FROM mytableWHERE condition AND/OR another_condition AND/OR …; 操作符号&#xff1a; 如果属性是字符串, 我们会用到字符串相关的一些操作符号&#xff0c;其中 LIKE&#xff08…

【数理统计实验(四)】方差分析

&#x1f349;CSDN小墨&晓末:https://blog.csdn.net/jd1813346972 个人介绍: 研一&#xff5c;统计学&#xff5c;干货分享          擅长Python、Matlab、R等主流编程软件          累计十余项国家级比赛奖项&#xff0c;参与研究经费10w、40w级横向 文…

FIT介绍-0

1、背景 FIT是flattened image tree的简称&#xff0c;它采用了device tree source file&#xff08;DTS&#xff09;的语法&#xff0c;生成的image文件也和dtb文件类似&#xff08;称做itb&#xff09;。 结构如下图&#xff1a; 其中image source file(.its)和device tree …

Midjourney绘图欣赏系列(八)

Midjourney介绍 Midjourney 是生成式人工智能的一个很好的例子&#xff0c;它根据文本提示创建图像。它与 Dall-E 和 Stable Diffusion 一起成为最流行的 AI 艺术创作工具之一。与竞争对手不同&#xff0c;Midjourney 是自筹资金且闭源的&#xff0c;因此确切了解其幕后内容尚不…

【alibaba funAsr-0.1.9实时语音转文字部署(内外网】

alibaba funAsr-0.1.9实时语音转文字部署&#xff08;内外网&#xff09; 官方参考文档&#xff1a; https://github.com/alibaba-damo-academy/FunASR/blob/main/runtime/docs/SDK_advanced_guide_online_zh.md 前提&#xff1a; 我的内网服务器是华为欧拉&#xff0c;arm…

【Linux】shell理解及linux权限解读(“花花公子Root”的自由人生)

目录 1.shell外壳理解 1.1 什么是shell外壳&#xff1a; 1.2 为什么存在shell外壳程序&#xff1a; 1.3外壳程序的具体工作阶段是怎么样的&#xff1f;&#xff08;招实习生&#xff0c;工作失败也不影响公司&#xff09; 2.linux下的权限的概念 2.1linux的用户 2.2.文件类型和…

基于c#语言的股票模拟交易软件的开发与实现

基于C#语言的股票模拟交易软件&#xff08;资管软件/分仓软件&#xff09;的开发与实现是一个涉及多个技术领域的项目。以下是一个大致的开发流程和实现要点&#xff1a; 一、项目概述 股票模拟交易软件旨在提供一个虚拟的股票交易环境&#xff0c;让用户可以在没有真实资金投…

MySQL常见的存储引擎介绍

我将为您详细讲解 MySQL 常见的存储引擎&#xff0c;以及它们的使用场景、特点、区别和优势。MySQL 支持多种存储引擎&#xff0c;每种存储引擎都有其特定的优势和局限性。了解这些存储引擎的特点和适用场景对于选择合适的存储引擎以及优化数据库性能至关重要。 1. InnoDB 存储…