二叉树_题目4:1008. 前序遍历构造二叉搜索树

128 阅读5分钟

题目4:1008. 前序遍历构造二叉搜索树

1008. 前序遍历构造二叉搜索树

前序遍历的题目

同类题目

100. 相同的树

前置知识

题目描述

给定一个整数数组,它表示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 <= 100
  • 1 <= preorder[i] <= 10^8
  • preorder 中的值 互不相同

思路分析

本题使用子问题分解(分治)思想来求解

首先考虑,如果单独抽出一个二叉树节点,它需要做什么事情?需要在什么时候(前/中/后序位置)做?其他的节点交给框架,递归函数会帮你在所有节点上执行相同的操作。

本题已经告诉我们是前序位置,因此,剩下的只需要抽出一个二叉树节点,看看它有什么事情要做

前序遍历的 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…