Hard

题目描述

我们从二叉树的根节点 root 开始进行深度优先搜索。

在遍历中的每个节点处,我们输出 D 条短划线(其中 D 是该节点的深度),然后输出该节点的值。如果节点的深度为 D,则其直接子节点的深度为 D + 1。根节点的深度为 0

如果节点只有一个子节点,那么保证该子节点为左子节点。

给出遍历输出 S,请你还原树并返回其根节点 root

示例 1:

输入:traversal = "1-2--3--4-5--6--7"
输出:[1,2,5,3,4,6,7]

示例 2:

输入:traversal = "1-2--3---4-5--6---7"
输出:[1,2,5,3,null,6,null,4,null,7]

示例 3:

输入:traversal = "1-401--349---90--88"
输出:[1,401,null,349,88,90]

提示:

  • 原始树中的节点数介于 11000 之间。
  • 每个节点的值介于 110^9 之间。

解题思路

这道题的核心思路是模拟前序遍历的逆过程。我们需要根据短划线的数量来确定节点的深度,然后根据深度关系来构建二叉树。

解题思路:

  1. 解析字符串:首先需要将字符串解析成 (深度, 值) 的形式。通过统计连续的短划线数量来确定深度。

  2. 使用栈维护路径:由于是前序遍历,我们可以使用栈来维护从根节点到当前节点的路径。栈中存储的是各个深度的节点。

  3. 构建树的过程

    • 对于每个新节点,首先根据其深度调整栈的大小(弹出深度大于等于当前节点深度的节点)
    • 然后将新节点作为栈顶节点的子节点
    • 根据题目规定,如果父节点没有左子节点,新节点作为左子节点;否则作为右子节点
  4. 关键观察:由于是前序遍历且只有一个子节点时保证是左子节点,我们可以通过栈来准确确定父子关系。

时间复杂度分析:需要遍历整个字符串一次,每个节点最多进出栈一次,所以时间复杂度为 O(n),其中 n 是字符串长度。

代码实现

class Solution {
public:
    TreeNode* recoverFromPreorder(string traversal) {
        stack<TreeNode*> stk;
        int i = 0;
        
        while (i < traversal.length()) {
            // 计算深度(短划线数量)
            int depth = 0;
            while (i < traversal.length() && traversal[i] == '-') {
                depth++;
                i++;
            }
            
            // 解析节点值
            int val = 0;
            while (i < traversal.length() && isdigit(traversal[i])) {
                val = val * 10 + (traversal[i] - '0');
                i++;
            }
            
            TreeNode* node = new TreeNode(val);
            
            // 调整栈,保持栈的大小等于深度
            while (stk.size() > depth) {
                stk.pop();
            }
            
            // 如果不是根节点,连接到父节点
            if (!stk.empty()) {
                TreeNode* parent = stk.top();
                if (parent->left == nullptr) {
                    parent->left = node;
                } else {
                    parent->right = node;
                }
            }
            
            stk.push(node);
        }
        
        // 返回根节点
        while (stk.size() > 1) {
            stk.pop();
        }
        return stk.top();
    }
};
class Solution:
    def recoverFromPreorder(self, traversal: str) -> Optional[TreeNode]:
        stack = []
        i = 0
        
        while i < len(traversal):
            # 计算深度(短划线数量)
            depth = 0
            while i < len(traversal) and traversal[i] == '-':
                depth += 1
                i += 1
            
            # 解析节点值
            val = 0
            while i < len(traversal) and traversal[i].isdigit():
                val = val * 10 + int(traversal[i])
                i += 1
            
            node = TreeNode(val)
            
            # 调整栈,保持栈的大小等于深度
            while len(stack) > depth:
                stack.pop()
            
            # 如果不是根节点,连接到父节点
            if stack:
                parent = stack[-1]
                if parent.left is None:
                    parent.left = node
                else:
                    parent.right = node
            
            stack.append(node)
        
        # 返回根节点
        return stack[0]
public class Solution {
    public TreeNode RecoverFromPreorder(string traversal) {
        Stack<TreeNode> stack = new Stack<TreeNode>();
        int i = 0;
        
        while (i < traversal.Length) {
            // 计算深度(短划线数量)
            int depth = 0;
            while (i < traversal.Length && traversal[i] == '-') {
                depth++;
                i++;
            }
            
            // 解析节点值
            int val = 0;
            while (i < traversal.Length && char.IsDigit(traversal[i])) {
                val = val * 10 + (traversal[i] - '0');
                i++;
            }
            
            TreeNode node = new TreeNode(val);
            
            // 调整栈,保持栈的大小等于深度
            while (stack.Count > depth) {
                stack.Pop();
            }
            
            // 如果不是根节点,连接到父节点
            if (stack.Count > 0) {
                TreeNode parent = stack.Peek();
                if (parent.left == null) {
                    parent.left = node;
                } else {
                    parent.right = node;
                }
            }
            
            stack.Push(node);
        }
        
        // 返回根节点
        while (stack.Count > 1) {
            stack.Pop();
        }
        return stack.Peek();
    }
}
var recoverFromPreorder = function(traversal) {
    const stack = [];
    let i = 0;
    
    while (i < traversal.length) {
        // 计算深度(短划线数量)
        let depth = 0;
        while (i < traversal.length && traversal[i]

复杂度分析

操作时间复杂度空间复杂度
字符串解析O(n)O(1)
栈操作O(n)O(h)
总计O(n)O(h)

其中 n 是字符串长度,h 是树的高度。每个字符最多被访问常数次,每个节点最多进出栈一次,所以时间复杂度是线性的。空间复杂度主要由栈的深度决定,最坏情况下等于树的高度。