力扣 227.基本计算器 II

问题描述

给你一个字符串表达式 s ,请你实现一个基本计算器来计算并返回它的值。

整数除法仅保留整数部分。

示例 1:

输入:s = "3+2*2"
输出:7

示例 2:

输入:s = " 3/2 "
输出:1

示例 3:

输入:s = " 3+5 / 2 "
输出:5

提示:

  1. 1 <= s.length <= 3 * 10^5
  2. s 由整数和算符 (’+’, ‘-’, ‘*’, ‘/’) 组成,中间由一些空格隔开
  3. s 表示一个 有效表达式
  4. 表达式中的所有整数都是非负整数,且在范围 [0, 231 - 1] 内
  5. 题目数据保证答案是一个 32-bit 整数

问题思路

思路1:双栈

​ 本题与224.基本计算器相比,加入了乘法运算和除法运算,由此出现了一个问题,如何维护运算符的优先级?为了能够复用224题的代码,这里我选择的策略是,先进行乘除法的计算,等遇到右括号或者扫描完毕后,统一计算加减法。

​ 同样,这里我们使用两个栈 number 和 symbol。分别用来存放操作数和操作符。

​ 对字符串进行一次扫描,规则如下:

​ 如果为空格,直接跳过。

​ 如果为‘+’、‘-’、‘(’、‘*’、‘/’,则直接入栈。

​ 如果为‘)’,则依次将符号栈的栈顶元素出栈,将数字栈中的操作数出栈,然后进行计算,计算后将结果存入数字栈,直到出栈符号为左括号时停止出栈。

​ 如果为数字,则首先继续扫描字符串,获得完整的数字。然后,根据符号栈栈顶元素进行不同的操作。如果是乘除法,则数字栈顶元素出栈,进行计算后将结果存入数字栈。如果为加号或左括号,则将操作数入栈。如果减号,则将操作数的相反数入栈。

​ 扫描结束后,符号栈内可能还有一些加号未运算,需要依次出栈并求和,得到最终结果。

​ 这里同样需要注意几个问题:

​ 第一,可能出现第一个数为负数,为了避免边界判断,我们在扫描前向数字栈入栈元素0。

​ 第二,左括号后的第一个数可能是负数,我们选择将 “(-” 替换为 “(0-”。

​ 实现代码如下,空间复杂度O(n),时间复杂度O(n)。运行时间258 ms。

class Solution {
	public int calculate(String s) {
		int n = s.length();
		char[] exp = s.toCharArray();
		Stack<Character> symbol = new Stack<Character>();
		Stack<Integer> number = new Stack<Integer>();
		// 防止第一个数为负数
		number.push(0);
		 // 将 (- 替换为 (0-
        s = s.replaceAll("\\(-", "(0-");
		int i = 0;
		while (i < n) {
			char c = exp[i++];
			if (c == ' ') {
				continue;
			} else if (c == '+' || c == '-' || c == '(' || c == '*' || c == '/') {
				symbol.push(c);
			} else {
				// 获取操作数
				int num;
				if (c == ')') {
					num = number.pop();
					char op = symbol.pop();
					while (op != '(') {
						int num2 = number.pop();
						System.out.println(num + " " + num2);
						if (op == '+') {
							num = num2 + num;
						}else if(op == '-'){
							num = num2 - num;
						}
						op = symbol.pop();
					};
				} else {
					num = c - '0';
					while (i < n && Character.isDigit(exp[i])) {
						num = 10 * num + exp[i++] - '0';
					}
				}
				// 若符号栈空, 则将操作数压栈
				System.out.println("num:" + num);
				if (symbol.isEmpty()) {
					number.push(num);
				} else {
					// 如果符号栈顶元素为运算符,则进行计算,并将结果压栈,如果为(,则直接将操作数压栈
					char top = symbol.peek();
					if (top == '*') {
						symbol.pop();
						number.push((number.pop() * num));
					} else if (top == '/') {
						symbol.pop();
						number.push((number.pop() / num));
					} else if (top == '-') {
						symbol.pop();
						symbol.push('+');
						number.push(-1 * num);
					} else {
						number.push(num);
					}
				}
			}
			System.out.println("number:" + number.toString());
			System.out.println("symbol:" + symbol.toString());
			System.out.println();
		}
		// 对栈内元素累加
		while (!symbol.isEmpty()) {
			symbol.pop();
			number.push(number.pop() + number.pop());
		}
		return number.pop();
	}
}

总结

​ 此题为了224题的代码能够复用,其实是采取了一个投机取巧的办法。事实上,计算应该发生在读取到符号时,而不是读取到数字时。

​ 选择在读取数字时计算,思路就是要根据该数字前的最后一个操作符计算出结果,然后将结果计算出并压栈,但是在有多个优先级的运算符时,往往需要考虑该数字后的第一个操作符的优先级问题,就会比较复杂,难以判断。

​ 一个好的思路就是在读取到符号时进行计算。首先定义一个字典类型,表示不同运算符的优先级。如果读取到运算符op,则将op之前直到左括号的所有优先级大于等于op的运算符出栈并计算;如果读取到右括号,则将直到左括号的所有运算符依次出栈并计算。这样做的好处就是保证了在同一括号内,运算符的优先级始终非严格单调递增,从而保证了结果的正确性。

​ 另外,这样做还可以及时的处理栈内与op优先级相同的运算,防止不满足交换律的运算出错,例如 3 - 2 + 3,如果到最后一起算的话,由于栈的先入后出的特性,会先计算2+3=5,在计算3-5=-2。很明显结果不正确。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值