[栈 字符串] 224. 基本计算器(栈的应用:符号栈+数字栈+出入栈规则、字符串解析) 227.基本计算器 II(出入栈规则、字符串解析)
224. 基本计算器(设计带括号的加减法)
题目链接:https://leetcode-cn.com/problems/basic-calculator/
分类:
- 栈(开辟符号栈、数字栈,设计出入栈规则,实现带括号的加减法)
- 数学/字符串(分析+实现基本计算器的功能,实质上是对字符串的解析:对数字、括号字符、加减法号的提取)
思路:栈 + 字符串解析
开辟两个栈sign和nums,sign用于存放运算符号,nums用于存放数字。
当不考虑括号时,栈的工作流程如下:
-
遇到运算符号时,就将符号压入sign栈中;
-
遇到数字num,就将nums栈顶top弹出作为因子1,弹出sign栈的栈顶作为运算符,当前数字num作为因子2,计算因子1 +/- 因子2,将运算结果存入nums栈。
这样就能实现基本的加减法。
当考虑括号时,就在无括号的基础上增加一定的处理:
-
遇到非括号的符号 或 左括号时,就将符号压入sign栈中;
-
遇到右括号,就将sign栈顶的左括号弹出,left-1,
接着做循环判断:如果此时sign栈顶为非括号符号,则弹出nums栈的两个栈顶,前一个栈顶做因子1,后一个栈顶做因子2,弹出sign的栈顶作为运算符,计算:因子1 +/- 因子2,运算结果存入nums栈。
循环直到栈为空或遇到左括号为止
-
遇到数字时,判断此时sign栈顶是什么符号:
- 如果是左括号或为空,则数字直接入nums栈;
- 如果是其他符号,则按之前的工作流程进行:就将nums栈顶top弹出作为因子1,弹出sign栈的栈顶作为运算符,当前数字num作为因子2,计算因子1 +/- 因子2,将运算结果存入nums栈。
实现遇到的问题:数字截取问题
数字不是只有个位数,所以遇到数字时要截取数字的左右边界,再转换成对应数字:
//寻找数字的左右边界
int start = i;
while(i < chars.length && chars[i] >= '0' && chars[i] <= '9') i++;
int num2 = Integer.parseInt(s.substring(start, i));
实现代码:
class Solution {
public int calculate(String s) {
Deque<Integer> nums = new ArrayDeque<>();//数字栈
Deque<Character> signs = new ArrayDeque<>();//符号栈
char[] chars = s.toCharArray();
int i = 0;
while(i < s.length()){
//遇到空格符直接跳过
if(chars[i] == ' '){
i++;
continue;
}
//遇到左括号、非括号运算符直接入符号栈
else if(chars[i] == '(' || chars[i] == '+' || chars[i] == '-'){
signs.push(chars[i]);
i++;
}
//遇到右括号
else if(chars[i] == ')'){
signs.pop();//弹出栈顶的左括号
while(!signs.isEmpty() && signs.peek() != '('){
int num2 = nums.pop();
int num1 = nums.pop();
char sign = signs.pop();
int res = helper(num1, num2, sign);
nums.push(res);
}
i++;
}
//遇到数字
else{
//寻找数字的左右边界
int start = i;
while(i < chars.length && chars[i] >= '0' && chars[i] <= '9') i++;
int num2 = Integer.parseInt(s.substring(start, i));
//符号栈为空或栈顶为左括号,数字直接入栈
if(signs.isEmpty() || signs.peek() == '(') nums.push(num2);
else{
int num1 = nums.pop();
char sign = signs.pop();
int res = helper(num1, num2, sign);
nums.push(res);
}
}
}
return nums.peek();
}
//执行计算的函数
public int helper(int num1, int num2, char sign){
switch(sign){
case '+':
return num1 + num2;
case '-':
return num1 - num2;
default:
return 0;
}
}
}
227. 基本计算器 II(设计无括号的加减乘除法)
题目链接:https://leetcode-cn.com/problems/basic-calculator-ii/
分类:
- 栈(只用一个栈,设计出入栈规则,实现无括号的加减乘除法)
- 数学/字符串(分析+实现基本计算器的功能,实质上是对字符串的解析:提取数字(难点)、加减乘除号)
题目分析
这题在224的基础上去掉了括号,但增加了乘除法,题目的考点在于:乘除法的优先级大于加减法要如何处理。
思路:栈 + 出入栈规则的妙用
-
和224一样仍然使用栈来实现,但不必分符号栈数字栈,只开辟一个栈,存放数字。
-
遍历字符串s:
- 如果遇到第一个数字或数字前面有加减号,将数字连同加减号一并加入栈中;
- 如果遇到的是 * 或 /,就取符号后的数字作为num2,弹出栈顶作为num1,计算num1 * 或/ num2,将计算结果存入栈中。
- 字符串s遍历结束后,将栈内所有元素相加就得到最终的结果。
实现时遇到的问题:数字提取问题
字符串内部可能存在空格符,所以在截取字符串上的一个数字时不能像224题那样使用substring + parseInt的方式,而要改为:遍历一个个字符+数值迭代更新的方法:
//根据当前的起点索引i寻找一个完整的数字
public int getNum(String s){
int num = 0;
for(; i < s.length(); i++){
if(s.charAt(i) == ' ') continue;
if(s.charAt(i) < '0' || s.charAt(i) > '9') break;
num = num * 10 + s.charAt(i) - '0';
}
return num;
}
- 按上面的方法提取数字可以避免字符串内部空格符的影响。
为了确保在获取数字后,字符串能够继续从数字右边界的下一个字符处开始工作,我们把字符串的工作指针设置为全局变量,因此for-i循环寻找数字的过程中 i++ 影响的是整个遍历过程的工作指针。
实现代码:
class Solution {
int i = 0;//字符串全局工作指针
public int calculate(String s) {
Deque<Integer> stack = new ArrayDeque<>();
while(i < s.length()){
//遇到空格符直接跳过
if(s.charAt(i) == ' ') i++;
//如果遇到+-号,则将其后的数字连同符号一起入栈
else if(s.charAt(i) == '+' || s.charAt(i) == '-'){
int sign = s.charAt(i) == '+' ? 1 : -1;
i++;
//寻找符号后面的数字
int num = getNum(s);
stack.push(sign * num);
}
//如果遇到的是*或/号,则取栈顶数字和其后的数字做乘或除法,再将计算结果入栈
else if(s.charAt(i) == '*' || s.charAt(i) == '/'){
char sign = s.charAt(i);
i++;
//先寻找其后的数字
//寻找符号后面的数字
int num2 = getNum(s);
int num1 = stack.poll();
int res = 1;
if(sign == '*') res = num1 * num2;
else res = num1 / num2;
stack.push(res);
}
//如果遇到的是数字,直接加入栈中
else{
int num = getNum(s);
stack.push(num);
}
}
//最后将栈内所有元素相加即可
int res = 0;
while(!stack.isEmpty()){
res += stack.poll();
}
return res;
}
//根据当前的起点索引i寻找一个完整的数字
public int getNum(String s){
int num = 0;
for(; i < s.length(); i++){
if(s.charAt(i) == ' ') continue;
if(s.charAt(i) < '0' || s.charAt(i) > '9') break;
num = num * 10 + s.charAt(i) - '0';
}
return num;
}
}