文章目录
一、题目
1. 题目描述
实现一个基本的计算器来计算一个简单的字符串表达式的值。
字符串表达式可以包含左括号 (
,右括号 )
,加号 +
,减号 -
,非负整数和空格 。
说明:
你可以假设所给定的表达式都是有效的。
请不要使用内置的库函数 eval。
2. 示例
示例1:
输入: “1 + 1”
输出: 2
示例2:
输入: " 2-1 + 2 "
输出: 3
实例3:
输入: “(1+(4+5+2)-3)+(6+8)”
输出: 23
3. 题目解析
非常复杂的一道实现计算器题,即需要考虑各种符号的优先级,又得考虑括号。
二、个人解法
1. 解法分析
使用和Leetcode小白试炼(20201210 基本计算器II)一样的方法来实现。不同的地方时对于括号的处理,遇到括号,递归调用本方法,把括号内的子串当做参数进行求和后得到一个整数。这个整数即可代表这个括号。
eg.
2. 失败反思
这是做的第一道困难题,思路明确,但是代码就是调试不出来。总结原因是关于递归不够熟悉,很多递归中的参数传递不知道如何处理。
失败原因1:把未遍历的子串作为参数传给递归函数,未处理好括号结束的标识符。
失败原因2:递归结束条件是遇到)
,未考虑递归结束后上一层递归如何继续处理。
调试修改的代码比较乱,就不再贴失败代码。
三、官网高星解法
1. 单栈递归法
(1)解法分析
整体思路和自己想的一样,值得学习的地方有两处:
a.递归传入原字符串,定义全局变量i,用于遍历字符串。这样不容易乱。(个人解法没想到用全局变量,导致每次找括号结束标识很难)
b.每次遍历到的字符先暂存到字符变量,防止中间触发递归函数后,遍历索引发生了变化,导致意外错误。(比如遍历到(
,这个时候进入递归,直至遇到)
跳出递归,这个时候遍历索引指向)
,正好符合本函数的跳出条件,导致函数直接终止。)
(2)代码
public int i = 0;
public int calculate(String s) {
int result = 0;
Stack<Integer> st = new Stack<>();
int temp = 0;
int kuohao = 0;
char fu = '+';
for (; i < s.length(); i++) {
// 这里要暂存一下当前遍历到的字符
char ch = s.charAt(i);
if (ch == '(') {
i++;
// 求括号内的值
// 这里递归过后,i会变化,若前面没有ch保存当前字符,这里当前字符会发生变化,变成),导致后续判断一直是跳出递归。
// 暂存可以保证递归出来后,符号还是(之前的符号
temp = calculate(s);
}
if (Character.isDigit(ch)) {
temp = temp * 10 + (s.charAt(i) - '0');
}
// 遇到非数字且非空格,这个时候分支处理,将之前的整数temp入栈,fu是上一个整数的符号
// 如果指针移动到最后一位,这个时候后面没有符号了,也需要分支处理
if ((!Character.isDigit(ch) && ch != ' ') || i == s.length() - 1 || ch == ')') {
switch (fu) {
case '+':
st.push(temp);
break;
case '-':
st.push(-temp);
break;
case '*':
st.push(st.pop() * temp);
break;
case '/':
st.push(st.pop() / temp);
break;
}
// 处理完上一个数,fu放当前符号
fu = ch;
temp = 0;
}
if (fu == ')' && i != s.length() - 1)
break;
}
// 栈内所有元素相加即为最终结果
while (!st.empty())
result += st.pop();
return result;
}
(3)算法分析
虽然代码中使用了递归,但是总体来说只遍历了一次字符串。
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
n
)
O(n)
O(n)
(4)提交截图
2. 后缀表达式
(1)解法分析
只提到了这种思路,没有写具体解法,自己尝试写的,不够精简。。
由于括号会干扰我们的正常计算顺序,若将正常的中缀表达式转换为后缀表达式,可以避免括号带来的计算顺序问题。后缀表达式的计算参考:牛牛与后缀表达式
中缀转后缀步骤:
- 实例化一个队列,一个符号栈,逐位遍历表达式。
- 如果是数字,合并至变量temp
- 如果上一位是数字,这一位非数字,则将temp入队(说明一一个操作数都放到了temp);若遍历至字符串末尾,且这一位是数字,则将temp入队
- 如果是左括号,直接入符号栈
- 如果是运算符,首先判断栈顶元素:若栈顶元素是左括号,该符号直接入栈;如果栈顶元素是符号,且优先级小于待处理符号,则直接入栈;若栈顶元素是符号,且优先级大于等于待处理符号,则先将栈顶元素出栈,入队,重复该步骤,直至待入栈符号能入栈。
- 如果是右括号,则将符号栈元素依次出栈入队,直至遇到左括号,左括号出栈但不入队。
后缀表达式求解步骤: - 实例化一个栈待用,遍历后缀表达式
- 遇到数字入栈
- 遇到符号,出栈俩元素进行操作,操作结果入栈
- 遍历结束后,栈顶元素即为结果
(2)代码
public int calculate(String s) {
int temp = 0;
Stack<String> st1 = new Stack<>();
// 队列中存的就是后缀表达式
Queue<String> st3 = new LinkedList<>();
Stack<String> st2 = new Stack<>();
for (int i = 0; i < s.length(); i++) {
// 合并连续的数字位
if (Character.isDigit(s.charAt(i))) {
temp = temp * 10 + (s.charAt(i) - '0');
}
//两种情况整数入栈:1.上一位是数字,这一位不是数字,说明这个整数遍历结束;2.字符串最后一位是数字
if((i>0&&Character.isDigit(s.charAt(i-1))&&!Character.isDigit(s.charAt(i)))||(i==s.length()-1&&Character.isDigit(s.charAt(i)))){
st3.add(String.valueOf(temp));
temp=0;
}
// 处理符号
if ((!Character.isDigit(s.charAt(i)) && s.charAt(i) != ' ') || i == s.length() - 1) {
switch (s.charAt(i)) {
case '(':
st2.push(String.valueOf(s.charAt(i)));
break;
case ')':
while (!st2.peek().equals("(")) {
st3.add(st2.pop());
// st1.push(st2.pop());
}
st2.pop();
break;
case '+':
// st3.add(String.valueOf(temp));
// temp = 0;
// 当栈不为空且当前优先级小于等于栈顶操作符优先级时,循环执行出栈并加入list操作。循环执行完再将当前操作符入栈
while (!st2.empty() && (st2.peek().equals("*") || st2.peek().equals("/") || st2.peek().equals("-") || st2.peek().equals("+"))) {
// st1.push(st2.pop());
st3.add(st2.pop());
}
st2.push("+");
break;
case '-':
// st3.add(String.valueOf(temp));
// temp = 0;
while (!st2.empty() && (st2.peek().equals("*") || st2.peek().equals("/") || st2.peek().equals("+") || st2.peek().equals("-"))) {
// st1.push(st2.pop());
st3.add(st2.pop());
}
st2.push("-");
break;
case '*':
// st3.add(String.valueOf(temp));
// temp = 0;
while (!st2.empty() && (st2.peek().equals("/") || st2.peek().equals("*"))) {
// st1.push(st2.pop());
st3.add(st2.pop());
}
st2.push("*");
break;
case '/':
// st3.add(String.valueOf(temp));
// temp = 0;
while (!st2.empty() && (st2.peek().equals("/") || st2.peek().equals("*"))) {
// st1.push(st2.pop());
st3.add(st2.pop());
}
st2.push("/");
break;
}
}
}
while (!st2.empty()) {
// st1.push(st2.pop());
st3.add(st2.pop());
}
//至此,中缀表达式转换后缀表达式完毕,st3为后缀表达式所在队列
Stack<Integer> stt = new Stack<>();
int result = 0;
while (!st3.isEmpty()) {
switch (st3.peek()) {
case "+":
st3.poll();
stt.push(stt.pop() + stt.pop());
break;
case "-":
st3.poll();
int tt = stt.pop();
stt.push(stt.pop() - tt);
break;
case "*":
st3.poll();
stt.push(stt.pop() * stt.pop());
break;
case "/":
st3.poll();
tt = stt.pop();
stt.push(stt.pop() / tt);
break;
default:
stt.push(Integer.parseInt(st3.poll()));
}
}
if(stt.empty()){
return 0;
}
return stt.pop();
}
(3)算法分析
用了两个栈一个队列,遍历了一次字符串,一次队列。
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
n
)
O(n)
O(n)