内卷之源:
https://leetcode-cn.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/
https://leetcode.cn/problems/cong-shang-dao-xia-da-yin-er-cha-shu-lcof/
题目描述:
给定两个整型数组 preorder 和 inorder ,其中 preorder 是二叉树的前序遍历, inorder 是同一棵树的中序遍历。构造二叉树,并按从上到下打印二叉树的每个节点,且同一层按从左至右的顺序,同一层不存在的节点输出null。
说明:(1) 输入由用例保证。
(2) preorder 和 inorder 均 无重复 元素。
(3) 1 <= preorder.length <= 3000。
(4) -3000 <= preorder[i], inorder[i] <= 3000。 //由(3)(4)知可使用短整型数据类型
测试用例:
Input | Output |
preorder = [3,9,20,15,7] inorder = [9,3,15,20,7] | [3,9,20,null,null,15,7] |
preorder = [3,9,20,15,6,12,7] inorder = [9,3,6,15,12,20,7] | [3,9,20,null,null,15,7,null,null,null,null,6,12,null,null] |
preorder = [1,2,4,8,9,5,10,3,6,7] inorder = [8,4,9,2,10,5,1,6,3,7] | [1,2,3,4,5,6,7,8,9,10,null,null,null,null,null] |
preorder = [-1] inorder = [-1] | [-1] |
preorder = [null] inorder = [null] | [null] |
思路分析:
树是通过递归定义的,大多数情况可以用递归去构造一棵树(还有迭代法),因此需要寻找相似的递归结构。本题类属二叉树遍历,已知根据 前序+中序 或 后序+中序 可以唯一确定一棵二叉树。
(1) 定义
前序遍历(先根遍历):首先访问根结点,然后遍历左子树,最后遍历右子树。
中序遍历(中根遍历):首先遍历左子树,然后访问根结点,最后遍历右子树。
后序遍历(后根遍历):首先遍历左子树,然后遍历右子树,最后访问根结点。
(2) 性质:
前序遍历序列:第一个元素一定是二叉树根结点。
后序遍历序列:最后一个元素一定是二叉树根结点。
中序遍历序列:根据前序或后序遍历序列确定的根结点,可确定中序遍历序列中左右子树范围,而前序或后序遍历序列中的左右子树与中序遍历序列只是顺序不同。
* 本题前序和中序遍历序列已知,由于是从前序遍历序列中挑选根节点(在遍历过程,根节点是一个局部的概念),然后确定其在中序遍历序列中的位置。因此对中序遍历序列构造一个Hash表可提升查找速度,以中序遍历序列中的各节点元素作为key,以各元素在中序遍历序列中的索引位置作为value。最后根据性质遍历序列即可构造二叉树(每个子树都有根,理解概念不要局限于整个二叉树的根节点)。
* 从上到下、同层从左至右打印二叉树的每个节点,使用BFS宽度/广度优先搜索算法,展开并检查树中的所有节点,使用队列适配器queue缓存同一层的所有节点指针。
本题四个输入示例对应的二叉树如下:
编程实现(C++):
/*
************************************************************
* @author SLF
* @version V1.0.0
* @date 20-May-2022
************************************************************
*/
#include <iostream>
#include <vector>
#include <unordered_map>
#include <queue>
#include <cmath>
#include <memory>
using namespace::std;
typedef struct TreeNode {
TreeNode():val(0),left(nullptr),right(nullptr) {}
TreeNode(short v):val(v),left(nullptr),right(nullptr) {}
TreeNode(short v, shared_ptr<TreeNode> l, shared_ptr<TreeNode> r):val(v),left(l),right(r) {}
short val;
shared_ptr<TreeNode> left;
shared_ptr<TreeNode> right;
} TreeNode;
class Solution {
public:
Solution() {}
~Solution() {}
/**
* 从上到下打印二叉树的每个节点,同一层按从左至右的顺序
**/
void levelOrderPrint(shared_ptr<TreeNode> const root)
{
vector<short> prt; //按顺序缓存各节点元素
queue<shared_ptr<TreeNode>> q; //缓存同一层节点指针
shared_ptr<TreeNode> tmp;
short n;
// 当树的根节点为空,直接返回空列表
if(nullptr != root)
{
q.push(root);
}
while(!q.empty())
{//逐层遍历
n = q.size();
for(short i = 0; n > i; ++i)
{//同层遍历
tmp = q.front();
prt.push_back(tmp->val);
q.pop();
if(nullptr != tmp->left)
{
q.push(tmp->left);
}
if(nullptr != tmp->right)
{
q.push(tmp->right);
}
}
}
if((n = prt.size()))
{
cout << prt[0];
for(short i = 1; n > i; ++i)
{
cout << ',' << prt[i];
}
cout << endl;
}
return;
}
/**
* 从上到下打印二叉树的每个节点,同一层按从左至右的顺序,同一层不存在的节点输出null
* 设计思路是将二叉树同一层最多数量pow(2, level-1)的节点都插入到队列中,包括不存在的节点位置。
* 如果仅插入已存在的节点,下一次遍历时很难确定节点在二叉树同层的哪个位置,尤其是多层结构,需要向前追溯。
**/
void levelOrderPrint_Comm(shared_ptr<TreeNode> const root)
{
unordered_map<short, short> unmap; //按顺序缓存各节点元素
queue<shared_ptr<TreeNode>> q; //缓存同一层节点指针(包括空节点)
shared_ptr<TreeNode> tmp;
short n = 0;
short lev = 0, inx = 0;
bool flag = false; //判断队列中缓存的同一层节点指针是否全是无效的空节点
if(nullptr == root)
{
cout << "null" << endl;
return;
}
q.push(root);
while(!flag)
{
flag = true;
++lev;
n = q.size();
for(short i = 0; n > i; ++i)
{
tmp = q.front();
if(nullptr == tmp)
{
// flag = true; //不能放此处,同层左边有,右边无
unmap[inx++] = __SHRT_MAX__;
q.pop();
// 下一层
q.push(nullptr);
q.push(nullptr);
continue;
}
else
{
flag = false;
unmap[inx++] = tmp->val;
}
q.pop();
if(nullptr != tmp->left)
{
q.push(tmp->left);
}
else
{
q.push(nullptr);
}
if(nullptr != tmp->right)
{
q.push(tmp->right);
}
else
{
q.push(nullptr);
}
}
}
// 上面while循环多计一层,最后queue中存放的全是nullptr
--lev;
n = 0;
for(short exp = 0; lev > exp; ++exp)
{// 计算二叉树节点总数(可以由外部输入)
// 第一层(根节点),元素个数最多 pow(2,0) 个
// 第二层,元素个数最多 pow(2,1) 个
// 第三层,元素个数最多 pow(2,2) 个
// ……
// 第N层,元素个数最多 pow(2,(N-1)) 个
n += pow(2,exp); //转 移位运算 或 等比数列求和公式 可提升效率
}
cout << unmap[0];
for(short i = 1; n > i; ++i)
{
if(__SHRT_MAX__ == unmap[i])
{
cout << ",null";
}
else
{
cout << ',' << unmap[i];
}
}
cout << endl;
return;
}
/**
* 根据前序和中序遍历序列构造二叉树
**/
shared_ptr<TreeNode> buildTree(const vector<short> &preorder, const vector<short> &inorder)
{
const short n = preorder.size();
// 针对中序遍历序列构造Hash表,方便定位根节点位置
for(short i = 0; n > i; ++i)
{// 使用中序遍历序列的节点元素作为容器的key,节点元素对应的索引位置作为容器的value
in_index[inorder[i]] = i;
}
return recurTraversal(preorder, inorder, 0, (n - 1), 0, (n - 1));
}
private:
/**
* 根据前序和中序遍历序列构造二叉树递归实现
**/
shared_ptr<TreeNode> recurTraversal(const vector<short> &preorder, const vector<short> &inorder, \
const short pre_index_left, const short pre_index_right, \
const short in_index_left, const short in_index_right) const
{// 需确保输入的原始序列正确,否则递归过程会出现访问野指针(该函数中不易主动规避)
if(pre_index_left > pre_index_right)
{
return nullptr;
}
// 前序遍历序列中的第一个节点就是根节点
const short pre_root = preorder[pre_index_left];
// 根节点存在
shared_ptr<TreeNode> root = make_shared<TreeNode>(pre_root); //智能指针自动管理内存
// 根据前序遍历序列根节点确定其在中序遍历序列中的位置
const short in_index_root = in_index.at(pre_root);
// 计算左子树中的节点数目(无需再计算右子树节点数目)
const short num_left_subtree = in_index_root - in_index_left;
// 递归地构造左子树
// 前序遍历序列中 从 (左边界+1) 开始的 num_left_subtree 个元素就对应了中序遍历序列中 从 (左边界) 开始到 (根节点定位-1) 的元素
root->left = recurTraversal(preorder, inorder, (pre_index_left +1), (pre_index_left + num_left_subtree), in_index_left, (in_index_root -1));
// 递归地构造右子树
// 前序遍历序列中 从 (左边界+1+左子树节点数目) 开始到 (右边界) 的元素就对应了中序遍历序列中 从 (根节点定位+1) 到 (右边界) 的元素
root->right = recurTraversal(preorder, inorder, (pre_index_left + num_left_subtree +1), pre_index_right, (in_index_root +1), in_index_right);
return root;
}
private:
unordered_map<short, short> in_index; //题设中已说明无重复元素
// unordered_multimap<short, short> in_index_multi;
};
int main(void)
{
vector<vector<short>> preorders = {{3,9,20,15,7}, {3,9,20,15,6,12,7}, {1,2,4,8,9,5,10,3,6,7}, {-1}, {}};
vector<vector<short>> inorders = {{9,3,15,20,7}, {9,3,6,15,12,20,7}, {8,4,9,2,10,5,1,6,3,7}, {-1}, {}};
Solution sl;
shared_ptr<TreeNode> tree;
const int n = preorders.size();
for(int i = 0; n > i; ++i)
{
tree = sl.buildTree(preorders[i], inorders[i]);
(void)sl.levelOrderPrint(tree);
(void)sl.levelOrderPrint_Comm(tree);
cout << endl;
}
return 0;
}
郑重提示:①解题思路非最优,覆盖条件可能不全,仅供练习参考。
②若有更佳思路或疑问,可在评论区留言相互讨论,不亦乐乎。
③本文不允许转载,若认可本文,可点赞收藏关注。