题目描述
思路分析:我们一般见到的问题是由构建好的二叉树求得其先、中、后序序列。这题是反其道行之。逆向构建一个二叉树。
我做二叉树、回溯以及动态规划 问题一般是通过分析现象 找到一定规律再做。
这个实例有点小,这里我们看一个 稍大些的案例,这样总结出的规律会更完善。

我们先找出这颗二叉树对应的先序,中序序列。
cpp
preorder = [3, 9, 8, 5, 4, 10, 20, 15, 7]
inorder = [4, 5, 8, 10, 9, 3, 15, 20, 7]
我们知道preorder其实就是: 中、左、右这样的遍历次序。
inorder就是:左、中、右这样的次序。
如果我们利用stack栈来存储 preorder先序遍历的结果,就拿4和之前的元素举例:
stack从根节点3持续存储元素到最左侧叶节点4,然后遍历其他节点。
而中序遍历会按(左、中、右)这样的优先级次序遍历整个二叉树。
所以:中序遍历优先遍历到的第一个节点, 会与stack存储先序遍历的栈顶元素 有重合。
而且,这个重合的元素**,是当前栈中 "左子树完全处理完毕" 的标志 ------ 意味着这个元素的左子树已经全部构建完成,接下来前序遍历的下一个元素,必然是这个元素的右子树(或其祖先的右子树)。**
用例子(4 的重合)拆解这个 "重合" 的意义
我们还是用 preorder = [3,9,8,5,4,10,20,15,7]、inorder = [4,5,8,10,9,3,15,20,7] 来具象化:
第一步:栈先存到最左叶节点 4(前序的 "中→左" 特性)
前序遍历先走 "中→左",所以栈会从根 3 开始,依次压入 9、8、5、4(直到最左叶节点),此时栈是 [3,9,8,5,4]。
- 这一步对应前序的
3→9→8→5→4,完全符合 "中、左、左、左、左" 的遍历逻辑。
第二步:中序的第一个节点是 4(中序的 "左→中→右" 特性)
中序遍历的第一个节点必然是整棵树的最左叶节点(4),所以 inorder[0] = 4,恰好等于此时的栈顶元素 4------ 这就是你说的 "重合"。
第三步:"重合" 的核心作用 ------ 标记左子树处理完毕
当栈顶元素 = 中序当前指针(index)时,说明:
- 这个栈顶元素(4)是 "左子树的终点"------ 它没有左子树(本身就是最左),左子树处理完毕;
- 接下来前序的下一个元素(10),不可能是 4 的左子树(已经没左了),只能是 "4 的祖先中,第一个还有右子树没处理的节点的右子树"。
再拆解 "重合后找右子树" 的过程(对应例子中 4 重合后的逻辑)
- 栈顶 4 和 inorder [index=0] 重合 → 弹出 4,index++(指向 5);
- 新的栈顶 5 和 inorder [index=1] 重合 → 弹出 5,index++(指向 8);
- 新的栈顶 8 和 inorder [index=2] 重合 → 弹出 8,index++(指向 10);
- 新的栈顶 9 和 inorder [index=3](10)不重合 → 停止弹出;
- 结论:8 是 "第一个还有右子树没处理的节点",前序的下一个元素 10,就是 8 的右子树。
最后再分析preorder的一般规律:
对于前序遍历 相邻两节点 u, v可能存在情况:
-
v是u的左节点。
-
u没有左儿子, v可能是u的右儿子/祖先的右儿子。
cpp
/**
* 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* buildTree(vector<int>& preorder, vector<int>& inorder) {
//对于前序遍历 相邻两节点 u, v可能存在情况:
// 1. v是u的左节点。
// 2. u没有左儿子, v可能是u的右儿子/祖先的右儿子。
if (!preorder.size()) return nullptr;
TreeNode* root = new TreeNode(preorder[0]);
stack<TreeNode*> stk; //存先序的节点
stk.push(root);
int index = 0; //中序的下标
for (int i = 1; i < preorder.size(); i ++) {
int preVal = preorder[i]; //现在遍历到的先序列表中的元素。
TreeNode* node = stk.top();
// 情况1:当前节点是栈顶节点的左子树
if (node->val != inorder[index]) {
node->left = new TreeNode(preVal);
stk.push(node->left);
}
// 情况2:当前节点preval是某个祖先节点的右子树
else {
while (!stk.empty() && stk.top()->val == inorder[index]) {
node = stk.top(); // 记录最后一个匹配的节点(右子树的父节点)
stk.pop();
index ++; //中序下标后移
}
node->right = new TreeNode(preVal);
stk.push(node->right);
}
}
return root;
}
};