LeetCode105. 从前序与中序遍历序列构造二叉树

题目描述

思路分析:我们一般见到的问题是由构建好的二叉树求得其先、中、后序序列。这题是反其道行之。逆向构建一个二叉树。

我做二叉树、回溯以及动态规划 问题一般是通过分析现象 找到一定规律再做。

这个实例有点小,这里我们看一个 稍大些的案例,这样总结出的规律会更完善。

我们先找出这颗二叉树对应的先序,中序序列。

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)时,说明:

  1. 这个栈顶元素(4)是 "左子树的终点"------ 它没有左子树(本身就是最左),左子树处理完毕;
  2. 接下来前序的下一个元素(10),不可能是 4 的左子树(已经没左了),只能是 "4 的祖先中,第一个还有右子树没处理的节点的右子树"。

再拆解 "重合后找右子树" 的过程(对应例子中 4 重合后的逻辑)

  1. 栈顶 4 和 inorder [index=0] 重合 → 弹出 4,index++(指向 5);
  2. 新的栈顶 5 和 inorder [index=1] 重合 → 弹出 5,index++(指向 8);
  3. 新的栈顶 8 和 inorder [index=2] 重合 → 弹出 8,index++(指向 10);
  4. 新的栈顶 9 和 inorder [index=3](10)不重合 → 停止弹出;
  5. 结论:8 是 "第一个还有右子树没处理的节点",前序的下一个元素 10,就是 8 的右子树。

最后再分析preorder的一般规律:

对于前序遍历 相邻两节点 u, v可能存在情况:

  1. v是u的左节点。

  2. 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;
    }
};
相关推荐
TracyCoder1231 小时前
LeetCode Hot100(63/100)——31. 下一个排列
数据结构·算法·leetcode
222you1 小时前
Mysql的索引以及底层的数据结构(面试)
数据结构·数据库·mysql
智者知已应修善业2 小时前
【不用第三变量交换2个数】2024-10-18
c语言·数据结构·c++·经验分享·笔记·算法
XiaoHu02072 小时前
C/C++数据结构与算法(第三弹)
数据结构
会编程的土豆2 小时前
c语言时间戳从入门到精通
linux·c语言·算法
所谓伊人,在水一方3332 小时前
【机器学习精通】第2章 | 优化算法深度解析:从梯度下降到自适应优化器
人工智能·python·算法·机器学习·信息可视化
Storynone2 小时前
【Day24】LeetCode:122. 买卖股票的最佳时机 II,55. 跳跃游戏,45. 跳跃游戏II,1005. K次取反后最大化的数组和
python·算法·leetcode
滴滴答滴答答2 小时前
机考刷题之 17&18&19&20&21&22 LeetCode 1248&121&43&93&62&63
算法·leetcode·职场和发展
for_ever_love__2 小时前
Objective-C学习 类别和扩展
学习·算法·objective-c