leetcode
leetcode 1451 ~ 1500
根据中缀表达式构造二叉表达式树

根据中缀表达式构造二叉表达式树

难度:

标签:

题目描述

代码结果

运行时间: 35 ms, 内存: 0.0 MB


/*
 * 思路:
 * 1. 将中缀表达式转换为后缀表达式(逆波兰表达式)。
 * 2. 使用栈构建二叉表达式树。
 * 
 * 第一步:
 * - 使用栈和优先级规则将中缀表达式转换为后缀表达式。
 * 
 * 第二步:
 * - 使用一个栈来构建树。当遇到操作数时,创建一个叶节点并推入栈中。
 * - 当遇到运算符时,弹出栈顶的两个节点作为该运算符的子节点,创建一个新节点并将其推入栈中。
 */

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

class TreeNode {
    char val;
    TreeNode left;
    TreeNode right;
    TreeNode(char x) { val = x; }
}

class Solution {
    public TreeNode expTree(String s) {
        List<Character> postfix = infixToPostfix(s);
        Deque<TreeNode> stack = new ArrayDeque<>();
        postfix.forEach(c -> {
            if (Character.isDigit(c)) {
                stack.push(new TreeNode(c));
            } else {
                TreeNode node = new TreeNode(c);
                node.right = stack.pop();
                node.left = stack.pop();
                stack.push(node);
            }
        });
        return stack.peek();
    }

    private List<Character> infixToPostfix(String s) {
        Deque<Character> stack = new ArrayDeque<>();
        Map<Character, Integer> precedence = Map.of(
            '+', 1, '-', 1, '*', 2, '/', 2, '(', 0
        );
        return IntStream.range(0, s.length())
            .mapToObj(s::charAt)
            .flatMap(c -> {
                if (Character.isDigit(c)) {
                    return Stream.of(c);
                } else if (c == '(') {
                    stack.push(c);
                    return Stream.empty();
                } else if (c == ')') {
                    List<Character> list = new ArrayList<>();
                    while (!stack.isEmpty() && stack.peek() != '(') {
                        list.add(stack.pop());
                    }
                    stack.pop();
                    return list.stream();
                } else {
                    List<Character> list = new ArrayList<>();
                    while (!stack.isEmpty() && precedence.get(stack.peek()) >= precedence.get(c)) {
                        list.add(stack.pop());
                    }
                    stack.push(c);
                    return list.stream();
                }
            })
            .collect(Collectors.toList());
    }
}

解释

方法:

这个题解采用了两个栈(操作符栈 ops 和数字栈 nums)来处理并构造表达式树。数字栈用于存储表达式中的数字(或已构造的子表达式树),而操作符栈用于存储操作符。在遍历输入字符串 s 时,根据字符的类型(数字、操作符、括号)进行不同的操作: 1. 数字直接转换为树节点并压入 nums 栈。 2. 操作符 '+', '-' 时,如果 ops 栈顶的操作符为 '+', '-', '*', '/', 需要进行计算以保持操作符的优先级,然后将当前操作符压栈。 3. 操作符 '*', '/' 类似,但只比较栈顶是否为 '*', '/', 因为这两者优先级更高。 4. 左括号 '(' 直接压栈,表示优先级的提升。 5. 右括号 ')' 表示一个括号内的表达式结束,需要执行计算直到遇到左括号。遇到左括号后,将其出栈。 最后,如果操作符栈中还有剩余操作符,继续执行计算,直到栈为空。最终,nums 栈顶的节点即为根节点。

时间复杂度:

O(n)

空间复杂度:

O(n)

代码细节讲解

🦆
在处理操作符 '+' 和 '-' 时,为什么需要检查并可能执行 mock_compute 函数,而处理 '*' 和 '/' 时只检查栈顶为 '*' 或 '/'?
在处理操作符 '+' 和 '-' 时,需要考虑到 '+' 和 '-' 的优先级比 '*' 和 '/' 低。因此,如果栈顶含有任何操作符(包括 '+'、'-'、'*'、'/'),都可能需要先执行这些操作符,以确保表达式的计算顺序和优先级正确。而在处理 '*' 和 '/' 时,由于它们的优先级较高,仅当栈顶为 '*' 或 '/' 时,才需要执行 mock_compute,即处理其他同样优先级高的操作符,否则可以保留在栈中等待下一步操作。
🦆
算法中如何处理数字多位的情况,例如 '23' 或 '105',因为示例中只提到了单个字符的数字处理?
题解中未直接处理多位数字的情况。在实际实现时,应当在遍历字符串时识别连续的数字字符。可以通过一个循环来读取完整的数字序列,直到遇到非数字字符,然后将读取到的完整数字转换为数值或节点。例如,使用一个临时变量存储数字字符串,当遇到非数字字符时,将这个字符串转换为数值,然后创建数值节点压入 nums 栈。
🦆
mock_compute 函数在遇到右括号 ')' 时如何确保所有在括号内的操作符都被处理掉,尤其是考虑到嵌套括号的情况?
在遇到右括号 ')' 时,mock_compute 函数会循环执行,直到栈顶为左括号 '('。这确保了所有在括号内的操作符都会被处理。每遇到一个右括号,算法都会处理直到最近的一个左括号,因此嵌套的括号也能正确处理,每一对括号内的表达式都会在遇到其对应右括号时完全解决。左括号之后的操作符和数字都被视为括号内的一部分,直到遇到对应的右括号。
🦆
在遍历完成输入字符串后,直接返回 nums 栈顶元素作为根节点是否安全?是否存在 nums 栈为空的情况?
直接返回 nums 栈顶元素作为根节点通常是安全的,前提是输入字符串有效且至少包含一个数字。如果输入字符串为空或不包含任何数字(例如只有操作符或不匹配的括号),则 nums 栈可能为空。因此,在实际的代码实现中,应该加入检查确保 nums 栈不为空。如果输入字符串有效并且表达式正确解析,nums 栈的顶层元素应该是整个表达式的根节点。

相关问题