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;
    }
};
相关推荐
月落归舟1 小时前
帮你从算法的角度来认识二叉树---(二)
算法·二叉树
清华都得不到的好学生2 小时前
数据结构->1.稀疏数组,2.数组队列(没有取模),3.环形队列
java·开发语言·数据结构
SilentSlot2 小时前
【数据结构】Hash
数据结构·算法·哈希算法
是娇娇公主~2 小时前
Lambda表达式详解
数据结构·c++
样例过了就是过了3 小时前
LeetCode热题100 柱状图中最大的矩形
数据结构·c++·算法·leetcode
wsoz3 小时前
Leetcode哈希-day1
算法·leetcode·哈希算法
阿Y加油吧3 小时前
LeetCode 二叉搜索树双神题通关!有序数组转平衡 BST + 验证 BST,小白递归一把梭
java·算法·leetcode
BlockChain8883 小时前
区块链的组件:从数据结构到去中心化共识
数据结构·去中心化·区块链
liuyao_xianhui4 小时前
优选算法_最小基因变化_bfs_C++
java·开发语言·数据结构·c++·算法·哈希算法·宽度优先
jimy14 小时前
C语言实现-----面向对象编程
c语言·数据结构