这道LeetCode 770题属于表达式解析与计算的经典难题,要求实现一个支持变量运算的多功能计算器。题目给出一个包含数字、变量和运算符的字符串表达式,需要完成以下核心功能:
x、y)的多项式表达式实际工程中这类需求常见于公式编辑器、科学计算软件等场景。我在金融量化系统开发中就遇到过类似需求——需要动态解析用户输入的投资组合计算公式。
面对这种结构化文本解析问题,通常有几种技术路线:
递归下降解析:手动实现词法分析和语法分析
逆波兰表达式转换:
第三方库调用:
eval()或Java的ScriptEngine经过实践对比,我推荐采用双栈法(操作数栈+运算符栈)结合运算符优先级表的方案。这种方案在LeetCode 227(基础计算器II)中验证过有效性,扩展到本题只需增加变量处理逻辑。
与常规计算器问题最大的区别在于变量处理:
3*x*y + 2*y + 1)x*y和y*x应合并)建议使用Map<变量组合, 系数>的结构存储多项式项,例如:
java复制class Term {
int coefficient;
List<String> variables; // 如["x","y"]
// 实现compareTo用于排序
}
首先需要将输入字符串转换为token流。注意处理:
+ - * / ( )java复制private List<String> tokenize(String s) {
List<String> tokens = new ArrayList<>();
int i = 0, n = s.length();
while (i < n) {
if (Character.isWhitespace(s.charAt(i))) {
i++;
continue;
}
if (s.charAt(i) == '(' || s.charAt(i) == ')') {
tokens.add(String.valueOf(s.charAt(i++)));
continue;
}
// 处理数字
if (Character.isDigit(s.charAt(i))) {
StringBuilder num = new StringBuilder();
while (i < n && Character.isDigit(s.charAt(i))) {
num.append(s.charAt(i++));
}
tokens.add(num.toString());
continue;
}
// 处理变量
if (Character.isLowerCase(s.charAt(i))) {
StringBuilder var = new StringBuilder();
while (i < n && Character.isLowerCase(s.charAt(i))) {
var.append(s.charAt(i++));
}
tokens.add(var.toString());
continue;
}
// 处理运算符
tokens.add(String.valueOf(s.charAt(i++)));
}
return tokens;
}
采用双栈法处理运算优先级,关键点:
java复制private Map<String, Integer> opPriority = Map.of(
"+", 1,
"-", 1,
"*", 2,
"/", 2
);
private Term calculate(Term a, Term b, String op) {
switch(op) {
case "+": return a.add(b);
case "-": return a.subtract(b);
case "*": return a.multiply(b);
case "/": return a.divide(b); // 注意题目要求整数除法
default: throw new IllegalArgumentException();
}
}
private List<Term> evaluate(List<String> tokens) {
Deque<List<Term>> numStack = new ArrayDeque<>();
Deque<String> opStack = new ArrayDeque<>();
for (String token : tokens) {
if (token.equals("(")) {
opStack.push(token);
} else if (token.equals(")")) {
while (!opStack.peek().equals("(")) {
// 弹出运算符计算
}
opStack.pop(); // 弹出左括号
} else if (opPriority.containsKey(token)) {
while (!opStack.isEmpty() &&
!opStack.peek().equals("(") &&
opPriority.get(opStack.peek()) >= opPriority.get(token)) {
// 处理栈顶高优先级运算符
}
opStack.push(token);
} else {
// 处理数字或变量
List<Term> term = parseTerm(token);
numStack.push(term);
}
}
// 处理剩余运算符
while (!opStack.isEmpty()) {
// 弹出计算
}
return numStack.pop();
}
Term类的核心方法实现示例:
java复制class Term implements Comparable<Term> {
// 同类项判断
public boolean isSameType(Term other) {
return this.variables.equals(other.variables);
}
// 加法实现
public Term add(Term other) {
if (!isSameType(other)) {
throw new IllegalArgumentException();
}
return new Term(this.coefficient + other.coefficient, this.variables);
}
// 乘法实现(变量组合合并)
public Term multiply(Term other) {
List<String> newVars = new ArrayList<>(this.variables);
newVars.addAll(other.variables);
Collections.sort(newVars); // 标准化变量顺序
return new Term(this.coefficient * other.coefficient, newVars);
}
// 实现Comparable接口用于排序
@Override
public int compareTo(Term other) {
if (this.variables.size() != other.variables.size()) {
return other.variables.size() - this.variables.size();
}
for (int i = 0; i < this.variables.size(); i++) {
int cmp = this.variables.get(i).compareTo(other.variables.get(i));
if (cmp != 0) return cmp;
}
return 0;
}
}
空表达式处理:
java复制if (expression == null || expression.trim().isEmpty()) {
return Collections.emptyList();
}
除法截断问题:
java复制// 在Term的divide方法中
public Term divide(Term other) {
if (other.coefficient == 0) throw new ArithmeticException();
if (!other.variables.isEmpty()) {
throw new UnsupportedOperationException();
}
return new Term(this.coefficient / other.coefficient, this.variables);
}
变量顺序标准化:
x*y和y*x被视为同类项预计算优化:
(1+2)*3,可在编译期直接计算出结果并行计算:
内存优化:
建议覆盖以下场景:
java复制@Test
public void testCases() {
// 纯数字运算
test("1 + 2 * 3", Arrays.asList("7"));
// 变量运算
test("x * y + x * z", Arrays.asList("x*y", "x*z"));
// 括号改变优先级
test("(x + y) * (x - y)", Arrays.asList("x*x", "-y*y"));
// 除法截断
test("5 / 2 * x", Arrays.asList("2*x"));
// 混合运算
test("x * 3 + y * 2 + x * y", Arrays.asList("x*y", "3*x", "2*y"));
}
在实际项目中,这种表达式计算器可以进一步扩展为:
函数支持:
sin、log等数学函数类型系统:
错误处理:
性能监控:
我在实际项目中实现过一个类似的计算引擎,用于金融衍生品定价。关键经验是:
重要提示:在除法运算时,如果除数是变量表达式(如
x/y),根据题目要求应该直接报错而非继续计算。这是很多初次尝试者容易忽略的边界条件。