栈与计算器
150. Evaluate Reverse Polish Notation
You are given an array of strings tokens
that represents an arithmetic expression in a Reverse Polish Notation.
Evaluate the expression. Return an integer that represents the value of the expression.
Note that:
- The valid operators are
'+'
,'-'
,'*'
, and'/'
. - Each operand may be an integer or another expression.
- The division between two integers always truncates toward zero.
- There will not be any division by zero.
- The input represents a valid arithmetic expression in a reverse polish notation.
- The answer and all the intermediate calculations can be represented in a 32-bit integer.
Example 1:
Input: tokens = ["2","1","+","3","*"]
Output: 9
Explanation: ((2 + 1) * 3) = 9
Example 2:
Input: tokens = ["4","13","5","/","+"]
Output: 6
Explanation: (4 + (13 / 5)) = 6
Example 3:
Input: tokens = ["10","6","9","3","+","-11","*","/","*","17","+","5","+"]
Output: 22
Explanation: ((10 * (6 / ((9 + 3) * -11))) + 17) + 5
= ((10 * (6 / (12 * -11))) + 17) + 5
= ((10 * (6 / -132)) + 17) + 5
= ((10 * 0) + 17) + 5
= (0 + 17) + 5
= 17 + 5
= 22
Constraints:
1 <= tokens.length <= 10^4
tokens[i]
is either an operator:"+"
,"-"
,"*"
, or"/"
, or an integer in the range[-200, 200]
.
思路
遇到数字字符串就转为数字并入栈,遇到运算符就弹出两个数字计算,然后把结果压栈。
逆波兰表达式:是一种后缀表达式,所谓后缀就是指运算符写在后面。
平常使用的算式则是一种中缀表达式,如 ( 1 + 2 ) * ( 3 + 4 ) 。
该算式的逆波兰表达式写法为 ( ( 1 2 + ) ( 3 4 + ) * ) 。
逆波兰表达式主要有以下两个优点:
- 去掉括号后表达式无歧义,上式即便写成 1 2 + 3 4 + * 也可以依据次序计算出正确结果。
- 适合用栈操作运算:遇到数字则入栈;遇到运算符则取出栈顶两个数字进行计算,并将结果压入栈中。
C++解法
class Solution {
public:
int evalRPN(vector<string>& tokens) {
// 力扣修改了后台测试数据,需要用longlong
stack<long long> st;
for (int i = 0; i < tokens.size(); i++) {
if (tokens[i] == "+" || tokens[i] == "-" || tokens[i] == "*" || tokens[i] == "/") {
long long num1 = st.top();
st.pop();
long long num2 = st.top();
st.pop();
if (tokens[i] == "+") st.push(num2 + num1);
if (tokens[i] == "-") st.push(num2 - num1);
if (tokens[i] == "*") st.push(num2 * num1);
if (tokens[i] == "/") st.push(num2 / num1);
} else {
st.push(stoll(tokens[i]));
}
}
long long result = st.top();
st.pop(); // 把栈里最后一个元素弹出(其实不弹出也没事)
return result;
}
};
实测使用int可以AC。
- 时间复杂度: O(n)
- 空间复杂度: O(n)
Java解法
class Solution {
public int evalRPN(String[] tokens) {
Deque<Integer> stack = new LinkedList();
for (String s : tokens) {
if ("+".equals(s)) { // leetcode 内置jdk的问题,不能使用==判断字符串是否相等
stack.push(stack.pop() + stack.pop()); // 注意 - 和/ 需要特殊处理
} else if ("-".equals(s)) {
stack.push(-stack.pop() + stack.pop());
} else if ("*".equals(s)) {
stack.push(stack.pop() * stack.pop());
} else if ("/".equals(s)) {
int temp1 = stack.pop();
int temp2 = stack.pop();
stack.push(temp2 / temp1);
} else {
stack.push(Integer.valueOf(s));
}
}
return stack.pop();
}
}
Python3解法
from operator import add, sub, mul
def div(x, y):
# 使用整数除法的向零取整方式
return int(x / y) if x * y > 0 else -(abs(x) // abs(y))
class Solution(object):
op_map = {'+': add, '-': sub, '*': mul, '/': div}
def evalRPN(self, tokens: List[str]) -> int:
stack = []
for token in tokens:
if token not in {'+', '-', '*', '/'}:
stack.append(int(token))
else:
op2 = stack.pop()
op1 = stack.pop()
stack.append(self.op_map[token](op1, op2)) # 第一个出来的在运算符后面
return stack.pop()
另一种可行,但因为使用eval()相对较慢的方法:
class Solution(object):
def evalRPN(self, tokens: List[str]) -> int:
stack = []
for token in tokens:
# 判断是否为数字,因为isdigit()不识别负数,故需要排除第一位的符号
if token.isdigit() or (len(token)>1 and token[1].isdigit()):
stack.append(token)
else:
op2 = stack.pop()
op1 = stack.pop()
# 由题意"The division always truncates toward zero",所以使用int()可以天然取整
stack.append(str(int(eval(op1 + token + op2))))
return int(stack.pop())
Go
func evalRPN(tokens []string) int {
stack := []int{}
for _, token := range tokens {
val, err := strconv.Atoi(token)
if err == nil {
stack = append(stack, val)
} else { // 如果err不为nil说明不是数字
num1, num2 := stack[len(stack)-2], stack[(len(stack))-1]
stack = stack[:len(stack)-2]
switch token {
case "+":
stack = append(stack, num1+num2)
case "-":
stack = append(stack, num1-num2)
case "*":
stack = append(stack, num1*num2)
case "/":
stack = append(stack, num1/num2)
}
}
}
return stack[0]
}
224. Basic Calculator
Given a string s
representing a valid expression, implement a basic calculator to evaluate it, and return the result of the evaluation.
Note: You are not allowed to use any built-in function which evaluates strings as mathematical expressions, such as eval()
.
Example 1:
Input: s = "1 + 1"
Output: 2
Example 2:
Input: s = " 2-1 + 2 "
Output: 3
Example 3:
Input: s = "(1+(4+5+2)-3)+(6+8)"
Output: 23
Constraints:
1 <= s.length <= 3 * 10^5
s
consists of digits,'+'
,'-'
,'('
,')'
, and' '
.s
represents a valid expression.'+'
is not used as a unary operation (i.e.,"+1"
and"+(2 + 3)"
is invalid).'-'
could be used as a unary operation (i.e.,"-1"
and"-(2 + 3)"
is valid).- There will be no two consecutive operators in the input.
- Every number and running calculation will fit in a signed 32-bit integer.
思路
左括号前是运算符,所以需要存储。
sum = st.top().first + st.top().second * sum;
其中sum是括号内数字和,st.top().first
是括号前数字和,st.top().second
是括号前运算符符号。
C++解法
class Solution {
public:
int calculate(string s) {
long long int sum = 0;
int sign = 1;
stack<pair<int,int>> st;
for(int i = 0; i < s.length(); i++){
if(isdigit(s[i])){
long long int num = 0;
while(i < s.size() && isdigit(s[i])){
num = num * 10 + (s[i] - '0');
i++;
}
i--;
sum += num * sign;
sign = 1;
}else if(s[i] == '('){
st.push({sum, sign});
sum = 0;
sign = 1;
}else if(s[i] == ')'){
sum = st.top().first + st.top().second * sum;
st.pop();
}else if(s[i] == '-'){
sign = -1 * sign;
}
}
return sum;
}
};
227. Basic Calculator II
Given a string s
which represents an expression, evaluate this expression and return its value.
The integer division should truncate toward zero.
You may assume that the given expression is always valid. All intermediate results will be in the range of [-2^31, 2^31 - 1]
.
Note: You are not allowed to use any built-in function which evaluates strings as mathematical expressions, such as eval()
.
Example 1:
Input: s = "3+2*2"
Output: 7
Example 2:
Input: s = " 3/2 "
Output: 1
Example 3:
Input: s = " 3+5 / 2 "
Output: 5
Constraints:
1 <= s.length <= 3 * 10^5
s
consists of integers and operators('+', '-', '*', '/')
separated by some number of spaces.s
represents a valid expression.- All the integers in the expression are non-negative integers in the range
[0, 2^31 - 1]
. - The answer is guaranteed to fit in a 32-bit integer.
思路
Approach 1: Using Stack
Intuition
We know that there could be 4 types of operations - addition (+)
, subtraction (-)
, multiplication (*)
and division (/)
. Without parenthesis, we know that, multiplication (*)
and (\)
operations would always have higher precedence than addition (+)
and subtraction (-)
based on operator precedence rules.
If we look at the above examples, we can make the following observations -
- If the current operation is addition
(+)
or subtraction(-)
, then the expression is evaluated based on the precedence of the next operation.
In example 1, 4+3
is evaluated later because the next operation is multiplication (3*5)
which has higher precedence.
But, in example 2, 4+3
is evaluated first because the next operation is subtraction (3-5)
which has equal precedence.
- If the current operator is multiplication
(*)
or division(/)
, then the expression is evaluated irrespective of the next operation. This is because in the given set of operations(+,-,*,/)
, the*
and/
operations have the highest precedence and therefore must be evaluated first.
In the above examples 3 and 4, 4*3
is always evaluated first irrespective of the next operation.
Using this intuition let's look at the algorithm to implement the problem.
Algorithm
Scan the input string s
from left to right and evaluate the expressions based on the following rules
- If the current character is a digit
0-9
( operand ), add it to the numbercurrentNumber
. - Otherwise, the current character must be an operation
(+,-,*, /)
. Evaluate the expression based on the type of operation.
- Addition
(+)
or Subtraction(-)
: We must evaluate the expression later based on the next operation. So, we must store thecurrentNumber
to be used later. Let's push the currentNumber in the Stack.
Stack data structure follows Last In First Out (LIFO) principle. Hence, the last pushed number in the stack would be popped out first for evaluation. In addition, when we pop from the stack and evaluate this expression in the future, we need a way to determine if the operation was Addition
(+)
or Subtraction(-)
. To simplify our evaluation, we can push-currentNumber
in a stack if the current operation is subtraction (-
) and assume that the operation for all the values in the stack is addition(+)
. This works because(a - currentNumber)
is equivalent to(a + (-currentNumber))
.
- Multiplication
(*)
or Division(/)
: Pop the top values from the stack and evaluate the current expression. Push the evaluated value back to the stack.
Once the string is scanned, pop from the stack and add to the result
.
Implementation
Complexity Analysis
- Time Complexity: O(n), where n is the length of the string s. We iterate over the string s at most twice.
- Space Complexity: O(n), where n is the length of the string s.
Approach 2: Optimized Approach without the stack
Intuition
In the previous approach, we used a stack to track the values of the evaluated expressions. In the end, we pop all the values from the stack and add to the result. Instead of that, we could add the values to the result beforehand and keep track of the last calculated number, thus eliminating the need for the stack. Let's understand the algorithm in detail.
Algorithm
The approach works similar to Approach 1 with the following differences :
- Instead of using a
stack
, we use a variablelastNumber
to track the value of the last evaluated expression. - If the operation is Addition
(+)
or Subtraction(-)
, add thelastNumber
to the result instead of pushing it to the stack. ThecurrentNumber
would be updated tolastNumber
for the next iteration. - If the operation is Multiplication
(*)
or Division(/)
, we must evaluate the expressionlastNumber * currentNumber
and update thelastNumber
with the result of the expression. This would be added to the result after the entire string is scanned.
Implementation
Complexity Analysis
- Time Complexity: O(n), where n is the length of the string s.
- Space Complexity: O(1), as we use constant extra space to store
lastNumber
,result
and so on.
C++解法
使用栈
class Solution {
public:
int calculate(string s) {
int len = s.length();
if (len == 0) return 0;
stack<int> stack;
int currentNumber = 0;
char operation = '+';
for (int i = 0; i < len; i++) {
char currentChar = s[i];
if (isdigit(currentChar)) {
currentNumber = (currentNumber * 10) + (currentChar - '0');
}
if (!isdigit(currentChar) && !iswspace(currentChar) || i == len - 1) {
if (operation == '-') {
stack.push(-currentNumber);
} else if (operation == '+') {
stack.push(currentNumber);
} else if (operation == '*') {
int stackTop = stack.top();
stack.pop();
stack.push(stackTop * currentNumber);
} else if (operation == '/') {
int stackTop = stack.top();
stack.pop();
stack.push(stackTop / currentNumber);
}
operation = currentChar;
currentNumber = 0;
}
}
int result = 0;
while (stack.size() != 0) {
result += stack.top();
stack.pop();
}
return result;
}
};
不用栈
class Solution {
public:
int calculate(string s) {
int length = s.length();
if (length == 0) return 0;
int currentNumber = 0, lastNumber = 0, result = 0;
char sign = '+';
for (int i = 0; i < length; i++) {
char currentChar = s[i];
if (isdigit(currentChar)) {
currentNumber = (currentNumber * 10) + (currentChar - '0');
}
if (!isdigit(currentChar) && !iswspace(currentChar) || i == length - 1) {
if (sign == '+' || sign == '-') {
result += lastNumber;
lastNumber = (sign == '+') ? currentNumber : -currentNumber;
} else if (sign == '*') {
lastNumber = lastNumber * currentNumber;
} else if (sign == '/') {
lastNumber = lastNumber / currentNumber;
}
sign = currentChar;
currentNumber = 0;
}
}
result += lastNumber;
return result;
}
};
Java解法
class Solution {
public int calculate(String s) {
if (s == null || s.isEmpty()) return 0;
int len = s.length();
Stack<Integer> stack = new Stack<Integer>();
int currentNumber = 0;
char operation = '+';
for (int i = 0; i < len; i++) {
char currentChar = s.charAt(i);
if (Character.isDigit(currentChar)) {
currentNumber = (currentNumber * 10) + (currentChar - '0');
}
if (!Character.isDigit(currentChar) && !Character.isWhitespace(currentChar) || i == len - 1) {
if (operation == '-') {
stack.push(-currentNumber);
}
else if (operation == '+') {
stack.push(currentNumber);
}
else if (operation == '*') {
stack.push(stack.pop() * currentNumber);
}
else if (operation == '/') {
stack.push(stack.pop() / currentNumber);
}
operation = currentChar;
currentNumber = 0;
}
}
int result = 0;
while (!stack.isEmpty()) {
result += stack.pop();
}
return result;
}
}
不用栈
class Solution {
public int calculate(String s) {
if (s == null || s.isEmpty()) return 0;
int length = s.length();
int currentNumber = 0, lastNumber = 0, result = 0;
char operation = '+';
for (int i = 0; i < length; i++) {
char currentChar = s.charAt(i);
if (Character.isDigit(currentChar)) {
currentNumber = (currentNumber * 10) + (currentChar - '0');
}
if (!Character.isDigit(currentChar) && !Character.isWhitespace(currentChar) || i == length - 1) {
if (operation == '+' || operation == '-') {
result += lastNumber;
lastNumber = (operation == '+') ? currentNumber : -currentNumber;
} else if (operation == '*') {
lastNumber = lastNumber * currentNumber;
} else if (operation == '/') {
lastNumber = lastNumber / currentNumber;
}
operation = currentChar;
currentNumber = 0;
}
}
result += lastNumber;
return result;
}
}