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

相关推荐
嗨嗨的迷子5 小时前
JDK 17 远程调试连不上 5005:从 attach timeout 到 JDWP 监听地址变更
java·开发语言
@小柯555m5 小时前
算法(移动零)
数据结构·算法·leetcode
Chase_______5 小时前
【Java杂项】为什么 long 可以自动转 float?宽化基本类型转换与精度丢失详解
java·开发语言·python
invicinble5 小时前
java数组相关的信息量
java·开发语言·python
小江的记录本5 小时前
【Java基础】Java 8-21新特性 :JDK17:密封类、模式匹配、Record类(附《思维导图》+《面试高频考点清单》)
java·数据结构·后端·python·mysql·面试·职场和发展
敲上瘾5 小时前
LangChain 消息机制与提示词模板指南
大数据·python·langchain
小江的记录本5 小时前
【Java基础】集合框架: ArrayList vs LinkedList 核心区别、扩容机制(附《思维导图》+《面试高频考点清单》)
java·数据库·python·mysql·spring·面试·maven
夕除5 小时前
spring boot 10
java·python·spring
牧瀬クリスだ5 小时前
Java线程——从创建第一个线程到休眠线程
java·开发语言