链接:https://leetcode.com/problems/basic-calculator/,使用的是通用的后缀表达式方法,也可以解答227. Basic Calculator II
对表达式求值,本质是为操作符operator(简写opt)找到操作数operand(简写opd)。对于中缀表达式,一个opt的左opd由左部分的某个suffix组成,右opd由右部分的某个prefix组成,当prefix suffix都找到后,可以将它们整体视为一个新opd进行之后的处理,如对a+b*c-d,当确定*左opd b,右opd c后,表达式可以换成a+e-d, e=b*c,+的后继由原左opd变成了新opd,-的前驱也由原右opd变成了新opd。(重点)而要判断opt的opd,实际上要以opd为中心,根据优先级和结合性看它属于左右哪个opt。
分析
以a + b * c - d为例,先不考虑括号,按照infix to postfix的算法处理,并探究深层的思想。我们维护两个栈,一个opd栈,一个opt栈,从左往右分析。
opd: a
opt: +
a进opd栈,再看+,a左边没有opt,所以a肯定是+的左opd,但a的右opd还不确定,可能只是b,也可能是b开头的子表达式。将+入栈,此时opd栈顶是opt栈顶的左opd,且右opd还未出现
opd: a b
opt: + current: *
opd: a b
opt: + *
b进入opd栈,opt栈顶+,opd栈顶b和current *组成表达式中的+ b *,+优先级低于*,b一定是*的左opd。将*入栈,此时同样处于opd栈顶是opt栈顶左opd,而右opd未出现的状态,且a是+的左opd,b是+的后继
opd: a b c
opt: + * current: -
表达式:a + (bc*) - d
opd: a bc*
opt: + current: -
表达式:(abc*+) - d
opd: abc*+
opt: -
c进opd栈,再看-,opt栈顶*,opd栈顶和current -组成表达式中* c -,*优先级高于-,c是*右opd,而opd次栈顶(不知道有没有这么个说法,为了方便表述自创的,即栈顶下面那个元素)b是*左opd,这么一来*的opd都确认了,将*与bc都出栈,再组成opd新栈顶,即原表达式的b * c将转化为一个整体bc*。
此时opd栈顶(bc*)是opt栈顶+后继,是current -前驱,对应表达式中+ (bc*) - ,由于左结合,(bc*)是+的右opd,而opd次栈顶a是+左opd,确认后将+与a(bc*)组成opd新栈顶。opd只剩一个了,肯定是-的左opd,-入opt栈。
opd: abc*+ d
opt: -
opd: abc*+d-
opt:
d进opd栈,后面再没有opt,所以opd栈顶肯定是opt栈顶的右opd,(abc*+) - d组合成abc*+d-
思考
两个栈想维护的是这样一种状态:opd[i]是opt[i]的左opd,opd[i+1]是opt[i]在表达式中的直接后继,即opt[i]右opd的第一个组成元素。当栈顶opt[i]的右opd找到后,会和opt[i] opd[i]组成新的opd[i]。而新的opd[i]也会继续充当opt[i-1]的直接后继。
opd: a b c d
opt: x y z u
处理opt之后处于此状态,两栈数量相同,一一对应
opd: a b c d e
opt: x y z u current: v
再进一个opd e,遇到opt v,u e v在表达式中相邻。
# priority(u) < priority(v) or stack opt is empty
opd: a b c d e
opt: x y z u v
当priority(u) < priority(v),或e之前的这些都不存在,即opt栈空时,e是v的左opd,将v进opt栈,得到目标状态。
# priority(u) > priority(v), including left associated
opd: a b c (deu)
opt: x y z current: v
如果priority(u) > priority(v),e是u的右opd,已知d是u的左opd,将deu结合进opd栈。此时(deu)将顶替d成为z的后继,也顶替e成为v的前驱。(重点)
收尾
opd: a b c
opt: x y no more opt
opd: a bcy
opt: x
当分析到表达式末尾,显然opd栈顶c是opt栈顶y的右opd,byc组合成opd新栈顶bcy。依此类推,两栈不断收缩,直到opd只剩一个元素,即表达式值。
带括号
括号相当于具体圈出一个子表达式整体求值,进入括号前和出括号后,差别只在于opd栈多一个值,即此子表达式的结果。遇到open paren,相当于两个栈视为空;遇到close paren,相当于分析完一个表达式,进行收尾工作,当出栈到open paren时结束。
代码
class Solution {
public:
int calculate(string s) {
vector<string> postOrder;
vector<char> opts{'#'}; // opt栈底放一个优先级最低的#,避免讨论空栈
// 构造后序表达式
for(int i = 0; i < s.size(); i++) {
char c = s[i];
if(c == ' ') continue;
else if(isdigit(c)) { // 注意不全是个位数
int cnt = 1, prev = i;
while(isdigit(s[i+1])) {
cnt++; i++;
}
postOrder.push_back(s.substr(prev, cnt));
} else if(c == '(') {
// '('直接进栈
opts.push_back(c);
} else if(c == ')') {
// pop until encountering open paren, then discard all two parens
while(opts.back() != '(') {
postOrder.push_back(string(1, opts.back()));
opts.pop_back();
}
opts.pop_back();
} else {
// pop until the priority of opt stack top is less
while(isLarger(opts.back(), c)) {
postOrder.push_back(string(1, opts.back()));
opts.pop_back();
}
opts.push_back(c);
}
}
while(opts.back() != '#') {
postOrder.push_back(string(1, opts.back()));
opts.pop_back();
}
// 计算后序表达式
stack<int> stk;
for(auto &str : postOrder) {
if(isdigit(str[0])) {
stk.push(stoi(str));
} else {
int tmp, opd2 = stk.top();
stk.pop();
int opd1 = stk.top();
stk.pop();
if(str == "+")
tmp = opd1 + opd2;
else if(str == "-")
tmp = opd1 - opd2;
else if(str == "*")
tmp = opd1 * opd2;
else if(str == "/")
tmp = opd1 / opd2;
stk.push(tmp);
}
}
return stk.top();
}
private:
// return true, if l's priority is larger than r
bool isLarger(char l, char r) {
switch(l) {
case '#': return false;
case '(': return false;
case '*': return true;
case '/': return true;
case '+': {
if(r == '*' || r == '/') return false;
return true;
}
case '-': {
if(r == '*' || r == '/') return false;
return true;
}
default: return true;
}
}
};