Hello 小伙伴们大家好呀,历经小半个月猛刷70题MarsCode AI刷题,我终于获得入营资格啦!第一篇笔记,我想从我刷的代码题目入手。今天具体来撕一撕栈的相关问题。
简单四则运算解析器
题目相关链接先放在这边~简单四则运算解析器 - MarsCode
问题阐述
问题阐述:小F面临一个编程挑战:实现一个基本的计算器来计算简单的字符串表达式的值。该字符串表达式有效,并可能包含数字(0-9)、运算符
+、-及括号()。注意,字符串中不包含空格。除法运算应只保留整数结果。请实现一个解析器计算这些表达式的值,且不使用任何内置的eval函数。
题解
对于这个问题我们需要如何下手呢?
- 首先,题目根本目的是将字符串转化为数字形式,然后进行四则运算后输出最终结果。
- 那么,我们可以从四则运算的法则进行解析。优先级是这样的:有
()则括号内数先进行计算;没有括号则先*和/,后+和-。 - 好嘟,接下来怎么处理这个冗长的算术表达式字符串呢?我会考虑从栈入手,因为出栈和入栈涉及先后问题,遵循后进先出原则,这样可以处理运算先后问题。
- 我选择使用两个栈,一个栈
numStack存放数字,一个栈opStack存放运算符号。
numStack规则:遍历字符串,如果当前字符是数字(isDigit(curChar)),那么使用int num = cur - '0';将字符转化为数字后,判断下一个字符是不是数字后【如果后一个是数字,那么就要将它与前一个结合起来形成一个完整的数字,如12+6,此时不能将1和2分开压入栈】,将处理过的数字压入numStack。opStack规则:- 如果当前字符是
(,那么直接压入栈。 - 如果是
+or-or*or/,则需要比较它们的优先级【这里我将+和-的优先级设为1,*和/的优先级设为2,其他为0】,如果当前符号优先级小于栈顶符号,则将numStack栈顶的前两个数字push出去【第一个push出去的数字设为b,第二个设为a】、opStack栈顶的第一个符号push出去【设为op】,操作顺序是:a op b,然后将算出的结果压入numStack;一直循环【也就是调用performOperation并结果入numStack栈】直到opStack空或者当前字符优先级大于栈顶符号,然后跳出循环,将当前符号压入opStack。 - 如果当前字符是
),那么只要opStack不为空并且栈顶符号部位左括号,就会一直重复计算操作performOperation并且将结果压入numStack。跳出循环后,将opStack栈顶符号也就是)弹出栈。
- 如果当前字符是
- 当所有字符都压入相应的栈后,只需要一直重复计算操作
performOperation直到opStack为空,此时最终结果就存储在numStack的栈顶,答案只需要returnnumStack的栈顶就可以。
出栈入栈这边数字和运算符的操作会比较复杂凌乱。
import java.util.Stack;
public class Main {
public static int solution(String expression) {
Stack<Integer> numStack = new Stack<>();
Stack<Character> opStack = new Stack<>();
for (int i = 0; i < expression.length(); i++) {
char cur = expression.charAt(i);
if (Character.isDigit(cur)) {
int num = cur - '0';
while (i + 1 < expression.length() && Character.isDigit(expression.charAt(i + 1))) {
num = num * 10 + (expression.charAt(++i) - '0');
}
numStack.push(num);
} else if (cur == '(') {
opStack.push(cur);
} else if (cur == ')') {
while (!opStack.isEmpty() && opStack.peek() != '(') {
int result = performOperation(numStack, opStack);
numStack.push(result);
}
opStack.pop();
} else if (isOperator(cur)) {
while (!opStack.isEmpty() && precedence(opStack.peek()) >= precedence(cur)) {
int result = performOperation(numStack, opStack);
numStack.push(result);
}
opStack.push(cur);
}
}
while (!opStack.isEmpty()) {
int result = performOperation(numStack, opStack);
numStack.push(result);
}
return numStack.pop();
}
private static boolean isOperator(char c) {
return c == '+' || c == '-' || c == '*' || c == '/';
}
private static int precedence(char op) {
switch (op) {
case '+':
case '-':
return 1;
case '*':
case '/':
return 2;
default:
return 0;
}
}
private static int performOperation(Stack<Integer> numStack, Stack<Character> opStack) {
int b = numStack.pop();
int a = numStack.pop();
char op = opStack.pop();
switch (op) {
case '+':
return a + b;
case '-':
return a - b;
case '*':
return a * b;
case '/':
return a / b;
}
return 0;
}
public static void main(String[] args) {
System.out.println(solution("1+1") == 2);
System.out.println(solution("3+4*5/(3+2)") == 7);
System.out.println(solution("4+2*5-2/1") == 12);
System.out.println(solution("(1+(4+5+2)-3)+(6+8)") == 23);
}
}
演算示例
用这里的测试用例举个例子,栈的状态用{}表示,设左到右分别对应栈底和栈顶。比如我们的string表达式是3+4*5/(3+2)。
- 遍历string
- 当前字符为3,后一个字符不是数字,将3压入
numStack。此时numStack状态:{3},opStack状态:{}。 - 当前字符为加号+,栈空,将+压入
opStack。此时numStack状态:{3},opStack状态:{+}。 - 当前字符为4,后一个字符不是数字,将4压入
numStack。此时numStack状态:{3, 4},opStack状态:{+}。 - 当前字符为乘号*,优先级高于栈顶符号,将*压入
opStack。此时numStack状态:{3, 4},opStack状态:{+, *}。 - 当前字符为5,后一个字符不是数字,将5压入
numStack。此时numStack状态:{3, 4, 5},opStack状态:{+, *}。 - 当前字符为除号/,优先级等于栈顶符号,进行
performOperation调用:numStack中5和4出栈,opStack中乘号出栈;b=5,a=4,op=*,则4 * 5 = 20。将计算结果20压入numStack, / 压入opStack。此时numStack状态:{3, 20},opStack状态:{+, /}。 - 当前字符为左括号(,将 ( 压入
opStack。此时numStack状态:{3, 20},opStack状态:{+, /, (}。 - 当前字符为3,后一个字符不是数字,将3压入
numStack。此时numStack状态:{3, 20, 3},opStack状态:{+, /, (}。 - 当前字符为加号+,优先级高于栈顶符号,将+压入
opStack。此时numStack状态:{3, 20, 3},opStack状态:{+, /, (, +}。 - 当前字符为2,后一个字符不是数字,将2压入
numStack。此时numStack状态:{3, 20, 3, 2},opStack状态:{+, /, (, +}。 - 当前字符为右括号),
opStack不为空且栈顶符号不为 ) ,则执行performOperation。执行后,numStack状态:{3, 20, 5},opStack状态:{+, /, (}。由于此时opStack栈顶为左括号 ( ,所以跳出循环,弹出 ( 。此时numStack状态:{3, 20, 5},opStack状态:{+, /}。 - string遍历结束
- 因为
opStack不为空,所以执行performOperation直到opStack为空。
- 第一次执行:
numStack状态:{3, 4},opStack状态:{+}。 - 第二次执行:
numStack状态:{7},opStack状态:{}。
- 得到最终结果:7
小结
芜湖,手撕结束,心情舒畅!
关于栈还是有很多学问的,对于什么时候要用到、怎么用等问题,我也还在刷题体会中。
BTW,大家不要排斥用AI进行解答。实在是没思路的时候问问AI,它会从题目最直接的角度引领我们进行代码编写,然后我们在这个基础上对我们的代码再进行优化、改善、总结。总之,不要闭门造车!!!
那么,我们下期再见咯!!