LeetCode105:前序中序构建二叉树(三解法)

题目LeetCode105

给定两个整数数组 preorderinorder ,其中 preorder 是二叉树的先序遍历inorder 是同一棵树的中序遍历,请构造二叉树并返回其根节点。

示例

输入: preorder = 3,9,20,15,7, inorder = 9,3,15,20,7
输出: 3,9,20,null,null,15,7

Python解法

解法一(递归DFS)

python 复制代码
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
        if not preorder or not inorder:
            return None
        
        # 快速找根在中序的位置
        val2idx = {val: i for i, val in enumerate(inorder)}
        
        def dfs(pre_l: int, pre_r: int, in_l: int, in_r: int) -> Optional[TreeNode]:
            if pre_l > pre_r:
                return None
            
            # 根节点
            root_val = preorder[pre_l]
            root = TreeNode(root_val)
            
            # 左子树大小
            pos = val2idx[root_val]
            left_size = pos - in_l
            
            # 递归左右
            root.left = dfs(pre_l + 1, pre_l + left_size, in_l, pos - 1)
            root.right = dfs(pre_l + left_size + 1, pre_r, pos + 1, in_r)
            
            return root
        
        return dfs(0, len(preorder) - 1, 0, len(inorder) - 1)

解释

这里解释enumerate哈希表映射

enumerate(inorder)

inorder 数组同时返回 下标 i + 元素 val 比如 inorder = [9,3,15,20,7]

遍历会得到:

python 复制代码
i=0, val=9
i=1, val=3
i=2, val=15
i=3, val=20
i=4, val=7

for i, val in enumerate(...)

循环取出每一组 (下标 i, 值 val)

{val: i ...}

构建字典:key = 节点值,value = 下标

最终得到:

python 复制代码
{
    9: 0,
    3: 1,
   15: 2,
   20: 3,
    7: 4
}

过程演示

解法二(迭代BFS)

python 复制代码
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
        if not preorder or not inorder:
            return None
        
        root = TreeNode(preorder[0])
        stack = [root]
        in_idx = 0  # 语义更清晰
        
        for i in range(1, len(preorder)):
            cur_val = preorder[i]
            node = stack[-1]
            
            # 一路向左走
            if node.val != inorder[in_idx]:
                node.left = TreeNode(cur_val)
                stack.append(node.left)
            
            # 回溯,找第一个能挂右孩子的节点
            else:
                while stack and stack[-1].val == inorder[in_idx]:
                    node = stack.pop()
                    in_idx += 1
                node.right = TreeNode(cur_val)
                stack.append(node.right)
        
        return root

解释

这道迭代法的本质是:

  1. 先一路沿着 preorder 扎到最左,全部入栈
  2. 当栈顶 == 中序当前指向,说明左子树走完了
  3. 不断出栈回溯 ,找到第一个能挂右孩子的节点
  4. 把当前 preorder 值挂成它的右孩子,继续

过程演示

Java解法

解法一(递归DFS)

java 复制代码
import java.util.HashMap;
import java.util.Map;

// 二叉树节点定义
class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;
    TreeNode() {}
    TreeNode(int val) { this.val = val; }
    TreeNode(int val, TreeNode left, TreeNode right) {
        this.val = val;
        this.left = left;
        this.right = right;
    }
}

class Solution {
    // 哈希表:存放中序数组 值 -> 下标,快速查找根位置
    private Map<Integer, Integer> valueToIndex;

    public TreeNode buildTree(int[] preorder, int[] inorder) {
        valueToIndex = new HashMap<>();
        int len = inorder.length;
        // 预处理,把中序所有元素下标存入哈希
        for (int i = 0; i < len; i++) {
            valueToIndex.put(inorder[i], i);
        }
        // 递归构建:前序左右边界,中序左右边界
        return dfs(preorder, 0, len - 1, 0, len - 1);
    }

    /**
     * 深度优先递归建树
     * @param pre 前序数组
     * @param preL 前序左边界
     * @param preR 前序右边界
     * @param inL 中序左边界
     * @param inR 中序右边界
     * @return 构建好的子树根节点
     */
    private TreeNode dfs(int[] pre, int preL, int preR, int inL, int inR) {
        // 区间无效,没有节点,返回空
        if (preL > preR) {
            return null;
        }
        // 前序第一个位置就是当前根节点
        int rootVal = pre[preL];
        TreeNode root = new TreeNode(rootVal);
        // 获取根节点在中序数组中的下标
        int rootPos = valueToIndex.get(rootVal);
        // 计算左子树节点个数:中序根位置 - 中序左边界
        int leftSubSize = rootPos - inL;

        // 递归构建左子树
        root.left = dfs(pre, preL + 1, preL + leftSubSize, inL, rootPos - 1);
        // 递归构建右子树
        root.right = dfs(pre, preL + leftSubSize + 1, preR, rootPos + 1, inR);

        return root;
    }
}

解法二(迭代BFS)

java 复制代码
import java.util.Stack;

class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;
    TreeNode() {}
    TreeNode(int val) { this.val = val; }
    TreeNode(int val, TreeNode left, TreeNode right) {
        this.val = val;
        this.left = left;
        this.right = right;
    }
}

class Solution {
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        // 空数组直接返回空树
        if (preorder.length == 0) return null;

        // 前序第一个元素为整棵树根节点
        TreeNode root = new TreeNode(preorder[0]);
        // 栈用来模拟递归深度,保存遍历路径上的节点
        Stack<TreeNode> stack = new Stack<>();
        stack.push(root);
        // 中序遍历指针,记录当前走到中序哪个位置
        int inIndex = 0;

        // 遍历前序剩余所有节点
        for (int i = 1; i < preorder.length; i++) {
            int curVal = preorder[i];
            // 获取当前栈顶节点
            TreeNode topNode = stack.peek();

            // 栈顶值 != 中序当前值:继续往左子树走
            if (topNode.val != inorder[inIndex]) {
                topNode.left = new TreeNode(curVal);
                stack.push(topNode.left);
            } 
            // 相等说明左子树走完,开始回溯找右子树挂载点
            else {
                // 不断出栈,同步移动中序指针
                while (!stack.isEmpty() && stack.peek().val == inorder[inIndex]) {
                    topNode = stack.pop();
                    inIndex++;
                }
                // 把当前节点作为回溯后节点的右孩子
                topNode.right = new TreeNode(curVal);
                stack.push(topNode.right);
            }
        }
        return root;
    }
}

C++解法

解法一(递归DFS)

cpp 复制代码
#include <vector>
#include <unordered_map>
using namespace std;

// 二叉树节点结构体
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 {
private:
    // 哈希映射:中序值对应下标
    unordered_map<int, int> val_idx_map;

    /**
     * 递归深度优先构建二叉树
     */
    TreeNode* dfs(vector<int>& pre, int preL, int preR, int inL, int inR) {
        // 区间越界,无节点
        if (preL > preR) return nullptr;
        // 取当前区间前序首位为根
        int root_val = pre[preL];
        TreeNode* root = new TreeNode(root_val);
        // 查到根在中序中的位置
        int root_pos = val_idx_map[root_val];
        // 统计左子树节点数量
        int left_size = root_pos - inL;

        // 构建左子树
        root->left = dfs(pre, preL + 1, preL + left_size, inL, root_pos - 1);
        // 构建右子树
        root->right = dfs(pre, preL + left_size + 1, preR, root_pos + 1, inR);
        return root;
    }

public:
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        int n = inorder.size();
        // 预处理建立值与下标的映射
        for (int i = 0; i < n; ++i) {
            val_idx_map[inorder[i]] = i;
        }
        // 初始左右边界都是整个数组
        return dfs(preorder, 0, n - 1, 0, n - 1);
    }
};

解法二(迭代BFS)

cpp 复制代码
#include <vector>
#include <stack>
using namespace std;

// 二叉树节点定义
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(nullptr) {}
};

class Solution {
public:
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        // 判空
        if (preorder.empty()) return nullptr;

        // 初始化根节点
        TreeNode* root = new TreeNode(preorder[0]);
        // 栈存储遍历路径节点
        stack<TreeNode*> stk;
        stk.push(root);
        // 中序遍历索引指针
        int in_idx = 0;

        // 遍历前序数组后续元素
        for (int i = 1; i < preorder.size(); ++i) {
            int cur_val = preorder[i];
            TreeNode* top = stk.top();

            // 不相等,挂载左子树
            if (top->val != inorder[in_idx]) {
                top->left = new TreeNode(cur_val);
                stk.push(top->left);
            }
            // 相等,回溯出栈,找右子树父节点
            else {
                while (!stk.empty() && stk.top()->val == inorder[in_idx]) {
                    top = stk.top();
                    stk.pop();
                    in_idx++;
                }
                // 挂载右孩子
                top->right = new TreeNode(cur_val);
                stk.push(top->right);
            }
        }
        return root;
    }
};

三种语言栈操作对比可参考之前文章末尾,如下链接https://blog.csdn.net/2602_95354978/article/details/161232212?spm=1011.2415.3001.5331

相关推荐
NE_STOP2 小时前
Vide Coding--AI编程工具的选择
java
用户8356290780512 小时前
使用 Python 操作 Word 内容控件
后端·python
通信小呆呆2 小时前
当算法有了“五感”:多模态数据融合如何向人体感官协同学习?
人工智能·学习·算法·机器学习·机器人
码云数智-园园2 小时前
C++20 Modules 模块详解
java·开发语言·spring
程序员黑豆2 小时前
JDK 下载安装与配置详细教程
java·前端·ai编程
benben0442 小时前
强化学习之DQN算法族(基于gymnasium开发)
算法
小宇宙Zz3 小时前
Maven依赖冲突
java·服务器·maven
swordbob3 小时前
NIO的channel中什么是 fd(File Descriptor,文件描述符)
java·开发语言·nio
咖啡八杯3 小时前
GoF设计模式——享元模式
java·spring·设计模式·享元模式