【剑指offer 2】 JZ7 重建二叉树
题目
栈辅助方法
通过栈辅助的迭代方式,利用前序和中序遍历的特性重构二叉树。其核心思路是模拟递归过程中的节点处理顺序,结合栈的先进后出特性管理父节点。以下是分步骤解析:
一、算法核心思路
-
前序遍历确定根节点
前序遍历的首元素为根节点(即pre[0]),首先创建根节点并压入栈中。 -
中序遍历定位左右子树分界
通过中序遍历数组(vin)的指针j,判断当前处理的节点是否属于左子树。若当前节点值与vin[j]相等,说明左子树已处理完毕,需转向处理右子树。 -
栈辅助回溯父节点
栈用于暂存未处理完右子树的父节点。当需要切换至右子树时,通过弹栈回溯到最近的未处理右子树的父节点。
二、代码分步解析
class Solution {
public:
TreeNode* reConstructBinaryTree(vector<int> pre, vector<int> vin) {
// 初始化
if (pre.empty() || vin.empty()) return nullptr;
TreeNode* root = new TreeNode(pre[0]); // 前序首元素为根
stack<TreeNode*> s;
TreeNode* cur = root;
int j = 0; // 中序指针
// 遍历前序数组剩余元素
for (int i = 1; i < pre.size(); i++) {
if (cur->val != vin[j]) {
// 情况1:当前元素是左子节点
cur->left = new TreeNode(pre[i]);
s.push(cur); // 父节点压栈
cur = cur->left; // 继续处理左子树
} else {
// 情况2:左子树处理完毕,转向右子树
j++;
while (!s.empty() && s.top()->val == vin[j]) {
cur = s.top(); // 回溯到最近的未处理右子树的祖先
s.pop();
j++;
}
// 添加右子节点
cur->right = new TreeNode(pre[i]);
cur = cur->right; // 处理右子树
}
}
return root;
}
};
关键步骤详解
-
初始化阶段
• 前序首元素创建根节点(root),中序指针j初始化为0。• 栈用于存储待处理右子树的父节点。
-
处理左子节点
• 条件:当前节点值不等于vin[j](说明当前前序元素属于左子树)。• 操作:将前序元素作为左子节点,父节点压栈,继续向左子树深入。
-
处理右子节点
• 触发条件:当前节点值等于vin[j](左子树处理完毕)。• 回溯父节点:通过弹栈找到第一个与
vin[j]不匹配的祖先节点(即该祖先的右子树尚未处理)。• 添加右子节点:将前序元素作为右子节点,并转向处理右子树。
三、算法复杂度与优化
• 时间复杂度:O(n),每个节点仅被访问一次。
• 空间复杂度:O(n),栈空间在最坏情况下存储所有节点。
对比递归法的优势
• 避免递归栈溢出:适合处理大规模数据。
• 逻辑紧凑:通过单层循环实现,减少函数调用开销。
局限性
• 依赖无重复值:若节点值重复,需额外处理(如哈希表记录索引)。
• 可读性较低:需结合前序和中序的指针变化理解逻辑。
四、示例验证
以输入 pre=[3,9,20,15,7] 和 vin=[9,3,15,20,7] 为例:
- 根节点为3,压栈。
j=0(vin[0]=9)。 pre[1]=9作为3的左子节点,压栈3,cur指向9。pre[2]=20时,cur->val=9与vin[j]=9匹配,回溯到3,添加20为右子节点。- 后续按相同逻辑处理右子树,最终构建完整二叉树。
五、总结
该算法通过栈模拟递归过程,巧妙利用前序和中序的特性,逐步构建二叉树。其核心在于通过中序指针j和栈的回溯机制,精准定位左右子树的分界点。如需进一步优化,可结合哈希表缓存中序索引,减少查找时间。

浙公网安备 33010602011771号