leetcode
leetcode 1501 ~ 1550
设计带解析函数的表达式树

设计带解析函数的表达式树

难度:

标签:

题目描述

代码结果

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


// LeetCode 1628: Design an Expression Tree With Evaluate Function
// Java solution using streams is not typical but here is an approach for demonstration

import java.util.*;
import java.util.function.Function;
import java.util.stream.*;

// Interface for the Node class
interface Node {
    public int evaluate();
}

// Abstract class representing a tree node
abstract class TreeNode implements Node {
    protected TreeNode left;
    protected TreeNode right;
    
    public TreeNode(TreeNode left, TreeNode right) {
        this.left = left;
        this.right = right;
    }
}

// Implementation of operand nodes
class OperandNode extends TreeNode {
    private int value;
    
    public OperandNode(int value) {
        super(null, null);
        this.value = value;
    }
    
    @Override
    public int evaluate() {
        return this.value;
    }
}

// Implementation of operator nodes
class OperatorNode extends TreeNode {
    private char operator;
    
    public OperatorNode(char operator, TreeNode left, TreeNode right) {
        super(left, right);
        this.operator = operator;
    }
    
    @Override
    public int evaluate() {
        int leftVal = left.evaluate();
        int rightVal = right.evaluate();
        switch (operator) {
            case '+': return leftVal + rightVal;
            case '-': return leftVal - rightVal;
            case '*': return leftVal * rightVal;
            case '/': return leftVal / rightVal;
            default: throw new UnsupportedOperationException("Invalid operator");
        }
    }
}

// Expression tree class
class TreeBuilder {
    public Node buildTree(String[] postfix) {
        Deque<TreeNode> stack = new ArrayDeque<>();
        Arrays.stream(postfix).forEach(token -> {
            if (isOperator(token)) {
                TreeNode right = stack.pop();
                TreeNode left = stack.pop();
                stack.push(new OperatorNode(token.charAt(0), left, right));
            } else {
                stack.push(new OperandNode(Integer.parseInt(token)));
            }
        });
        return stack.pop();
    }
    
    private boolean isOperator(String token) {
        return "+-*/".contains(token);
    }
}

// Usage example
public class Main {
    public static void main(String[] args) {
        String[] postfix = {"3", "4", "+", "2", "*", "7", "/"};
        TreeBuilder builder = new TreeBuilder();
        Node root = builder.buildTree(postfix);
        System.out.println(root.evaluate()); // Output should be 2
    }
}

解释

方法:

这个题解是基于后缀表达式 (逆波兰表示法) 构建表达式树的方法,并提供了一个节点接口 Node 来计算表达式树的值。首先定义了一个抽象基类 Node,每个节点有左右子节点和节点值。节点值可以是数字或者操作符('+', '-', '*', '/')。如果节点值是数字,则直接返回该值;如果是操作符,则递归地计算左右子节点的值并执行相应的运算。TreeBuilder 类中的 buildTree 方法接受后缀表达式的数组,使用递归的方式从后向前读取数组元素,构建表达式树。如果当前元素是数字,则直接创建节点;如果是操作符,则先递归构建右子树和左子树,因为在后缀表达式中,操作符总是跟在对应的操作数之后。

时间复杂度:

O(n)

空间复杂度:

O(n)

代码细节讲解

🦆
为什么构建表达式树时选择从后缀表达式的末尾到开始递归构造,而不是从开始到末尾?
后缀表达式(逆波兰表示法)的设计使得每个操作符直接跟随其操作数,因此从后向前读取时,最先遇到的操作符可以直接应用于其紧接着的操作数。这种从后向前的构造方式符合栈的后进先出(LIFO)原则,使得构建表达式树时可以更自然地处理操作符和操作数的关系,无需额外的数据结构来暂存未处理的操作符。
🦆
构建表达式树的过程中,如果遇到操作符如何确保已经有足够的操作数节点供操作?
在构建表达式树时,如果按照正确的后缀表达式规则,每个操作符之前应该有足够的操作数。构建过程中,每遇到一个操作符,就会连续从栈(或递归过程中的调用栈)中取出两个已构建的子树(或节点)作为该操作符的右子节点和左子节点。如果在操作符处理时不能正确地取出两个节点,这通常意味着后缀表达式本身不符合规范。
🦆
在递归构建表达式树的过程中,如何处理非法的后缀表达式,例如操作符数量不正确或操作数过多的情况?
非法的后缀表达式,如操作符和操作数比例不当,应在构建树之前或构建过程中检测并处理。可以通过跟踪操作数和操作符的数量来验证表达式的有效性。例如,最终操作数数量应该比操作符数量多一个。如果在构建过程中出现不匹配情况,如操作符无法找到足够的操作数,应抛出错误或返回特定的错误信息,指示表达式不合法。
🦆
计算表达式值时,除法操作使用了整数除法`//`。如果输入的后缀表达式预期结果为非整数,这种处理方式是否合适?
如果后缀表达式中涉及的计算预期结果为非整数,则使用整数除法`//`可能不合适,因为它会导致结果向下取整,从而可能与预期结果不符。在这种情况下,应根据实际需求调整节点类中除法的实现,可能需要使用浮点除法`/`来确保得到精确的小数结果。此外,还应考虑除数为零的错误处理和浮点数精度问题。

相关问题