题目4:1008. 前序遍历构造二叉搜索树
前序遍历的题目
同类题目
前置知识
- 无
题目描述
给定一个整数数组,它表示BST(即 二叉搜索树 )的 先序遍历 ,构造树并返回其根。
保证 对于给定的测试用例,总是有可能找到具有给定需求的二叉搜索树。
二叉搜索树 是一棵二叉树,其中每个节点, Node.left 的任何后代的值 严格小于 Node.val , Node.right 的任何后代的值 严格大于 Node.val。
二叉树的 前序遍历 首先显示节点的值,然后遍历Node.left,最后遍历Node.right。
示例 1:
输入:preorder = [8,5,1,7,10,12]
输出:[8,5,10,1,7,null,12]
示例 2:
输入: preorder = [1,3]
输出: [1,null,3]
提示:
1 <= preorder.length <= 1001 <= preorder[i] <= 10^8preorder中的值 互不相同
思路分析
本题使用子问题分解(分治)思想来求解
首先考虑,如果单独抽出一个二叉树节点,它需要做什么事情?需要在什么时候(前/中/后序位置)做?其他的节点交给框架,递归函数会帮你在所有节点上执行相同的操作。
本题已经告诉我们是前序位置,因此,剩下的只需要抽出一个二叉树节点,看看它有什么事情要做。
前序遍历的 preorder[0] 是树的根节点,根据二叉搜索树的特性,左子树的节点都比根节点小,右子树的节点都比跟节点大。对于一个节点有下面3件事情。
- 从
preorder中找到第一个比preorder[0]大的节点,并根据此节点坐标将preorder分成左右两部分,分别对应左子树和右子树。 - 左部分节点都严格小于根结点,可以递归构建成根结点的左子树;
- 右部分节点都严格大于根结点,可以递归构建成根结点的右子树。
方法一: findLoc 函数使用顺序查找 O(N)。
程序代码
//https://leetcode.cn/problems/construct-binary-search-tree-from-preorder-traversal/
//1008. 前序遍历构造二叉搜索树
#include<iostream>
#include<vector>
using namespace std;
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:
// 从l到r 范围内找大于preorder[l] 的数并返回其下标,返回-1 说明没有找到
int findLoc(vector<int>& preorder,int l,int r)
{
for(int i=l+1;i<=r;i++)
{
if(preorder[i] > preorder[l])
{
return i;
}
}
return -1;
}
TreeNode* createTree(vector<int>& preorder,int l,int r)
{
// base case
if(l>r)
{
return nullptr;
}
// preorder[l] 为头节点
// preorder 中第一个大于 preorder[l] 的元素为右子树
// 找到第一个大于preorder[l] 的坐标
int loc = findLoc(preorder,l,r);
TreeNode *cur = new TreeNode(preorder[l]);
if(loc > 0)
{
cur->left = createTree(preorder,l+1,loc-1);
cur->right = createTree(preorder,loc,r);
}else // -1 说明没有右子树
{
cur->left = createTree(preorder,l+1,r);
}
return cur;
}
TreeNode* bstFromPreorder(vector<int>& preorder) {
int len = preorder.size()-1;
return createTree(preorder,0,len);
}
};
复杂度分析:
- 时间复杂度:
O(N*N),N为节点数量,前序遍历的时间为O(N),使用顺序找左右子树分界线的时候时间复杂度为O(N) - 空间复杂度:
O(N), 数组的大小为N
思路分析
方法二:将 findLoc方法中的顺序查找换成二分查找,时间复杂度O(logN)。
程序代码
//https://leetcode.cn/problems/construct-binary-search-tree-from-preorder-traversal/
//1008. 前序遍历构造二叉搜索树
#include<iostream>
#include<vector>
using namespace std;
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:
// 从l到r 范围内找大于preorder[l] 的数并返回其下标,返回-1 说明没有找到
int findLoc(vector<int>& preorder,int l,int r)
{
// 这里换成二分搜索,找到大于preorder[l] 的最左位置
int res = -1;
while(l <= r )
{
int mid = l + (r - l)/2;
if(preorder[mid] > num)
{
res = mid;
r = mid - 1;
}else
{
l = mid + 1;
}
}
return res;
}
TreeNode* createTree(vector<int>& preorder,int l,int r)
{
// base case
if(l>r)
{
return nullptr;
}
// preorder[l] 为头节点
// preorder 中第一个大于 preorder[l] 的元素为右子树
// 找到第一个大于preorder[l] 的坐标
int loc = findLoc(preorder,l,r);
TreeNode *cur = new TreeNode(preorder[l]);
if(loc > 0)
{
cur->left = createTree(preorder,l+1,loc-1);
cur->right = createTree(preorder,loc,r);
}else // -1 说明没有右子树
{
cur->left = createTree(preorder,l+1,r);
}
return cur;
}
TreeNode* bstFromPreorder(vector<int>& preorder) {
int len = preorder.size()-1;
return createTree(preorder,0,len);
}
};
复杂度分析:
- 时间复杂度:
O(NlogN),N为节点数量,前序遍历的时间为O(N),使用二分查找左右子树分界线的时候时间复杂度为O(logN) - 空间复杂度:
O(N), 数组的大小为N
思路分析
方法三:利用辅助栈实现递归
- 根节点
preorder[0]入栈sta,对后续节点进行迭代 - 节点大于栈顶元素值
preorder[i] > sta.top->val,则将栈顶弹出,直到后续节点小于栈顶元素值,将preorder[i]作为最后弹出的节点右子节点 - 节点小于栈顶原始值
preorder[i] < sta.top->val,则将preorder[i]作为栈顶节点左子节点 - 将子节点压栈
程序代码
class Solution {
public:
TreeNode* bstFromPreorder(vector<int>& preorder) {
int len = preorder.size();
if(len == 0)
{
return nullptr;
}
stack<TreeNode*> sta;
TreeNode *root = new TreeNode(preorder[0]);
sta.push(root);
for(int i=1;i<len;i++)
{
TreeNode *cur = sta.top();
TreeNode *newnode = new TreeNode(preorder[i]);
while(!sta.empty() && sta.top()->val < newnode->val)
{
cur = sta.top();
sta.pop();
}
if(cur->val < preorder[i])
{
cur->right = newnode;
}else
{
cur->left = newnode;
}
sta.push(newnode);
}
return root;
}
};
复杂度分析
- 时间复杂度:
O(N),仅扫描前序遍历一次。 - 空间复杂度:
O(N),用来存储栈和二叉树。
题目感悟
分治的思想:定义一个递归函数,通过子问题(子树)的答案推导出原问题
更多相关知识:github.com/pingguo1987…