NC137. 表达式求值(medium)
方法一:双栈法(表达式计算的通用解法)
思路:这里借鉴了三叶大佬的题解,自己在这写一遍加深印象。
对于任何表达式而言,我们可以使用两个栈nums和ops:
- nums:存放所有的数字
- ops:存放所有的数字之外的操作
然后从前往后遍历,对遍历的字符分情况讨论: - 空格:跳过
(
:直接加入ops
中,等待与之匹配的)
)
:使用现有的nums
和ops
进行计算,直到遇到左边最近的一个左括号为止,计算结果放到nums
中数字
:从当前位置开始继续往后取,将一个连续数字整体取出,加入nums
+ - * / ^ %
:需要将操作放入ops
中。在放入之前先把栈内可以算的都算掉(只有栈内运算符
比当前运算符
优先级高/同等,才进行运算),使用现有的nums
和ops
进行计算,直到没有操作或遇到左括号,计算结果放到nums
另外,还有一些特殊情况需要考虑:- 对于空格,我们先将这些空格替换掉
- 由于第一个数可能是负数,我们可以先往
nums
中添加一个0以减少边界判断 - 为防止()内出现的首个字符为运算符,将所有的空格去掉,并将
(-
替换为(0-
,(+
替换为(0+
。 - 为防止中间结果溢出,可以将nums中存放为
long
而不是int
import java.util.*;
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
* 返回表达式的值
* @param s string字符串 待计算的表达式
* @return int整型
*/
// 优先级维护表
static Map<Character, Integer> map = new HashMap<>();
// 数字栈
static Deque<Integer> nums = new ArrayDeque<>();
// 操作栈
static Deque<Character> ops = new ArrayDeque<>();
public int solve (String s) {
// write code here
// 细节处理
s = s.replaceAll(" ", "");
s.replaceAll("\\(-","\\0-");
s.replaceAll("\\(+","\\0+");
map.put('-', 1);
map.put('+', 1);
map.put('*', 2);
map.put('/', 2);
map.put('%', 2);
map.put('^', 3);
char[] cs = s.toCharArray();
int n = s.length();
nums.addLast(0);
for (int i = 0; i < n; i++) {
char c = cs[i];
if (c == '(') {
// 左括号直接加入
ops.addLast(c);
} else if (c == ')') {
// 遇到右括号
while (!ops.isEmpty()) {
if (ops.peekLast() != '(') {
// 没遇到左括号,我就一直取数做加减乘除法
calc(nums, ops);
} else {
// 遇到左括号,结束了!
ops.pollLast();
break;
}
}
} else {
if (isNumber(c)) {
// 遇到数字(可能是一个很长的数字,比如123456这样的)
int u = 0;
int j = i;
while (j < n && isNumber(cs[j])) {
u = u * 10 + (cs[j++] - '0');
}
// 将这个数字存到nums栈中
nums.addLast(u);
i = j - 1; // 此时j在一个非数字的位置
} else {
// 遇到加减乘除运算操作
while (!ops.isEmpty() && ops.peekLast() != '(') {
char prev = ops.peekLast();
if (map.get(prev) >= map.get(c)) {
// 栈中的操作符优先级高于当前遍历到的优先级
calc(nums,ops);
} else {
// 比栈中的优先级高
break;
}
}
// 将当前遍历到的优先级放到ops栈中
ops.addLast(c);
}
}
}
while (!ops.isEmpty() && ops.peekLast() != '(') {
calc(nums, ops);
}
return nums.peekLast();
}
static boolean isNumber(char c) {
return Character.isDigit(c);
}
static void calc(Deque<Integer> nums, Deque<Character> ops) {
// 没有数或者只有一个数或者没有操作数
if (nums.isEmpty() || nums.size() < 2) return;
if (ops.isEmpty()) return;
int b = nums.pollLast(), a = nums.pollLast();
char op = ops.pollLast();
int ans = 0;
if (op == '+') {
ans = a + b;
} else if (op == '-') {
ans = a - b;
} else if (op == '*') {
ans = a * b;
} else if (op == '/') {
ans = a / b;
} else if (op == '^') {
ans = (int)Math.pow(a, b);
} else if (op == '%') {
ans = a % b;
}
nums.addLast(ans);
}
}
时间复杂度: O(N),遍历一遍字符串即可。
空间复杂度: O(N),需要使用双栈,但双栈的长度之和为字符串长度。
总结:这道题好难啊,做了两遍都没写出来,三月份写过一遍,现在写还是写不出来。还是得温故而知新啊,并且这种解法可以秒杀所有的关于计算器设计和表达式求值的题目。