本文主要是介绍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:
- [3,2,1] 中的最大值是 3 ,左边部分是 [] ,右边部分是 [2,1] 。
输入: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)。
这篇关于LeetCode 654.最大二叉树的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!