这道LeetCode 770题"基本计算器IV"属于表达式解析与符号计算的经典难题。题目要求实现一个能够处理带变量的多项式表达式计算器,输出规范化的计算结果。与基础版计算器问题相比,其特殊之处在于需要处理:
2*a*b + 3*c + 1)在实际工程中,这类需求常见于公式编辑器、科学计算软件等场景。我在金融量化系统开发中就遇到过类似需求——需要动态解析用户输入的风险计算公式,其中包含多种市场参数变量。
经过多次尝试,最终确定采用双栈解析法的改进方案:
java复制class Solution {
private Map<String, Integer> evalMap;
public List<String> basicCalculatorIV(String expression,
String[] evalvars,
int[] evalints) {
// 实现代码...
}
}
设计Polynomial类封装多项式运算逻辑:
Map<List<String>, Integer>存储单项式(如["a","b"]对应2表示2ab)add(), multiply(), evaluate()等核心方法compareTo()满足题目输出顺序要求注意:单项式的变量列表必须保持字母序,这是合并同类项的关键前提
处理表达式字符串时容易忽略的几个细节:
replaceAll("\\s+", " ")先规范化-5识别为整体而非-和5java复制private List<String> tokenize(String s) {
List<String> tokens = new ArrayList<>();
StringBuilder sb = new StringBuilder();
for (char c : s.toCharArray()) {
if (c == ' ') continue;
if (c == '(' || c == ')' || c == '+' || c == '-' || c == '*') {
if (sb.length() > 0) {
tokens.add(sb.toString());
sb.setLength(0);
}
tokens.add(String.valueOf(c));
} else {
sb.append(c);
}
}
if (sb.length() > 0) tokens.add(sb.toString());
return tokens;
}
采用Dijkstra双栈算法的改进版本:
*比+/-更高的优先级a - b → a + (-b))java复制private static final Map<String, Integer> PRIORITY = Map.of(
"(", 0,
"+", 1,
"-", 1,
"*", 2
);
while (!ops.isEmpty() && PRIORITY.get(ops.peek()) >= currPri) {
Polynomial b = nums.pop();
Polynomial a = nums.pop();
nums.push(applyOp(a, b, ops.pop()));
}
多项式乘法是算法核心难点,需要实现:
Arrays.sort())java复制public Polynomial multiply(Polynomial that) {
Polynomial res = new Polynomial();
for (Map.Entry<List<String>, Integer> e1 : this.terms.entrySet()) {
for (Map.Entry<List<String>, Integer> e2 : that.terms.entrySet()) {
List<String> vars = new ArrayList<>();
vars.addAll(e1.getKey());
vars.addAll(e2.getKey());
Collections.sort(vars);
int coeff = e1.getValue() * e2.getValue();
res.addTerm(vars, coeff);
}
}
return res;
}
"123"要转换为常数项((a + b)*c)需要正确处理TreeMap自动维护变量顺序java复制// 变量替换优化
evalMap = new HashMap<>();
for (int i = 0; i < evalvars.length; i++) {
evalMap.put(evalvars[i], evalints[i]);
}
以下是经过LeetCode测试通过的完整解决方案:
java复制class Solution {
private Map<String, Integer> evalMap;
public List<String> basicCalculatorIV(String expression, String[] evalvars, int[] evalints) {
evalMap = new HashMap<>();
for (int i = 0; i < evalvars.length; i++) {
evalMap.put(evalvars[i], evalints[i]);
}
return parse(expression).toList();
}
private Polynomial parse(String expr) {
List<String> tokens = tokenize(expr);
Deque<Polynomial> nums = new ArrayDeque<>();
Deque<String> ops = new ArrayDeque<>();
for (int i = 0; i < tokens.size(); i++) {
String token = tokens.get(i);
if (token.equals("(")) {
ops.push(token);
} else if (token.equals(")")) {
while (!ops.peek().equals("(")) {
nums.push(applyOp(nums.pop(), nums.pop(), ops.pop()));
}
ops.pop();
} else if (isOp(token)) {
while (!ops.isEmpty() && priority(ops.peek()) >= priority(token)) {
nums.push(applyOp(nums.pop(), nums.pop(), ops.pop()));
}
ops.push(token);
} else {
Polynomial p;
if (Character.isDigit(token.charAt(0))) {
p = new Polynomial(Integer.parseInt(token));
} else if (evalMap.containsKey(token)) {
p = new Polynomial(evalMap.get(token));
} else {
p = new Polynomial(Arrays.asList(token), 1);
}
nums.push(p);
}
}
while (!ops.isEmpty()) {
nums.push(applyOp(nums.pop(), nums.pop(), ops.pop()));
}
return nums.isEmpty() ? new Polynomial() : nums.pop();
}
private Polynomial applyOp(Polynomial b, Polynomial a, String op) {
switch (op) {
case "+": return a.add(b);
case "-": return a.add(b.multiply(new Polynomial(-1)));
case "*": return a.multiply(b);
default: throw new IllegalArgumentException();
}
}
// 辅助方法省略...
}
class Polynomial {
Map<List<String>, Integer> terms;
public Polynomial() { terms = new TreeMap<>(this::compareTerm); }
public Polynomial(int val) {
this();
if (val != 0) terms.put(new ArrayList<>(), val);
}
public Polynomial(List<String> vars, int coeff) {
this();
if (coeff != 0) terms.put(new ArrayList<>(vars), coeff);
}
// 其他方法实现...
}
假设表达式长度为N:
String.intern()减少重复变量名的存储在实际工程实现中,可以考虑引入多项式运算的缓存机制。我在量化系统开发中就建立了一个表达式缓存池,将解析过的表达式AST缓存起来,当相同表达式再次出现时直接复用计算结果,性能提升显著。