题目
标题和出处
标题:Lisp 语法解析
难度
10 级
题目描述
要求
给定一个类似 Lisp 语句的表达式 expression \texttt{expression} expression,求出其计算结果。
表达式语法如下所示:
- 表达式可以为整数,let 表达式,add 表达式,mult 表达式,或赋值的变量。表达式的结果总是一个整数。
- 整数可以是正整数、负整数、 0 \texttt{0} 0。
- let 表达式的形式为 "(let v 1 e 1 v 2 e 2 ... v n e n expr)" \texttt{"(let v}_\texttt{1}\texttt{ e}_\texttt{1}\texttt{ v}_\texttt{2}\texttt{ e}_\texttt{2}\texttt{ ... v}_\texttt{n}\texttt{ e}_\texttt{n}\texttt{ expr)"} "(let v1 e1 v2 e2 ... vn en expr)",其中 let 表达式总是以字符串 "let" \texttt{"let"} "let" 表示,接下来会跟随一对或多对交替变量或表达式,表示第一个变量 v 1 \texttt{v}_\texttt{1} v1 被赋值表达式 e 1 \texttt{e}_\texttt{1} e1 的值,第二个变量 v 2 \texttt{v}_\texttt{2} v2 被赋值表达式 e 2 \texttt{e}_\texttt{2} e2 的值,以此类推;最终 let 表达式的值为 expr \texttt{expr} expr 表达式的值。
- add 表达式表示为 "(add e 1 e 2 )" \texttt{"(add e}_\texttt{1}\texttt{ e}_\texttt{2}\texttt{)"} "(add e1 e2)",其中 add 表达式总是以字符串 "add" \texttt{"add"} "add" 表示,add 表达式总是有两个表达式 e 1 \texttt{e}_\texttt{1} e1、 e 2 \texttt{e}_\texttt{2} e2,add 表达式的结果是 e 1 \texttt{e}_\texttt{1} e1 表达式的值与 e 2 \texttt{e}_\texttt{2} e2 表达式的值之和。
- mult 表达式表示为 "(mult e 1 e 2 )" \texttt{"(mult e}_\texttt{1}\texttt{ e}_\texttt{2}\texttt{)"} "(mult e1 e2)",其中 mult 表达式总是以字符串 "mult" \texttt{"mult"} "mult" 表示,mult 表达式总是有两个表达式 e 1 \texttt{e}_\texttt{1} e1、 e 2 \texttt{e}_\texttt{2} e2,mult 表达式的结果是 e 1 \texttt{e}_\texttt{1} e1 表达式的值与 e 2 \texttt{e}_\texttt{2} e2 表达式的值之积。
- 对于这道题,变量名的规则限定为一个较小的子集。变量名以小写字母开始,之后跟随零个或多个小写字符或数字。另外为了方便, "add" \texttt{"add"} "add"、 "let" \texttt{"let"} "let" 和 "mult" \texttt{"mult"} "mult" 会被定义为保护字,不会作为变量名。
- 最后,需要说明作用域的概念。计算变量名所对应的表达式时,在计算上下文中,首先检查最内层作用域(按括号计),然后按顺序依次检查外层作用域。题目保证每个表达式都是合法的。有关作用域的更多详细信息,请参阅示例。
示例
示例 1:
输入:
expression
=
"(let
x
2
(mult
x
(let
x
3
y
4
(add
x
y))))"
\texttt{expression = "(let x 2 (mult x (let x 3 y 4 (add x y))))"}
expression = "(let x 2 (mult x (let x 3 y 4 (add x y))))"
输出:
14
\texttt{14}
14
解释:在表达式
(add
x
y)
\texttt{(add x y)}
(add x y) 中,在检查变量
x
\texttt{x}
x 的值时, 从变量所在上下文的最内层向最外层检查。由于首先遇到
x
=
3
\texttt{x = 3}
x = 3,因此
x
\texttt{x}
x 的值是
3
\texttt{3}
3。
示例 2:
输入:
expression
=
"(let
x
3
x
2
x)"
\texttt{expression = "(let x 3 x 2 x)"}
expression = "(let x 3 x 2 x)"
输出:
2
\texttt{2}
2
解释:let 语句中的赋值运算按顺序处理。
示例 3:
输入:
expression
=
"(let
x
1
y
2
x
(add
x
y)
(add
x
y))"
\texttt{expression = "(let x 1 y 2 x (add x y) (add x y))"}
expression = "(let x 1 y 2 x (add x y) (add x y))"
输出:
5
\texttt{5}
5
解释:第一个
(add
x
y)
\texttt{(add x y)}
(add x y) 计算结果是
3
\texttt{3}
3,并且将此值赋给了
x
\texttt{x}
x。第二个
(add
x
y)
\texttt{(add x y)}
(add x y) 计算结果就是
3
+
2
=
5
\texttt{3} + \texttt{2} = \texttt{5}
3+2=5。
数据范围
- 1 ≤ expression.length ≤ 2000 \texttt{1} \le \texttt{expression.length} \le \texttt{2000} 1≤expression.length≤2000
- expression \texttt{expression} expression 没有前导空格和后置空格
- expression \texttt{expression} expression 中的不同部分都使用一个空格分隔
- 题目保证表达式的结果和所有中间结果都在 32 \texttt{32} 32 位整数范围内
- 题目保证给定的表达式有效且可以计算得到一个整数
解法
思路和算法
由于 Lisp 表达式中存在括号嵌套以及变量赋值,因此解析 Lisp 表达式需要使用到栈和哈希表这两种数据结构。由于内层表达式的结果会被外层表达式使用,因此还需要存储每一层的表达式,为了得到每一层的表达式的类型和最后一个部分的内容,使用双端队列存储表达式的每一个部分。基于上述分析,解析 Lisp 表达式需要使用两个栈,分别用于存储每一层的表达式和每一层的哈希表,两个栈分别为表达式栈和哈希表栈。
在遍历 Lisp 表达式之前,首先将一个空表达式(即空双端队列)入表达式栈以及将一个空哈希表入哈希表栈,目的是为了确保两个栈不为空,便于处理 Lisp 表达式。从左到右遍历 Lisp 表达式的过程中,需要将 Lisp 表达式的每个部分分离,并对每个部分执行相应的操作。Lisp 表达式中的部分包括以下五种:
- 左括号,表示一层的开始;
- 右括号,表示一层的结束;
- 表达式类型,可能为 let、add 和 mult;
- 变量名,为一个或多个连续的小写字母和数字,以小写字母开头;
- 整数。
其中,左括号一定和表达式类型相连,因此在遇到左括号之后即可处理表达式类型,不需要单独处理表达式类型。对于其余各部分,需要在遍历过程中处理。
-
如果遇到左括号,则是一层的开始,执行如下操作。
-
继续遍历后面的字符,知道遍历到空格,此时得到表达式类型。
-
新建表达式,将表达式类型加入表达式,然后将表达式入表达式栈。
-
如果表达式类型是 let 表达式,则该表达式可能会使用到上一层表达式的变量赋值,因此基于哈希表栈的栈顶哈希表新建一个哈希表(即上一层哈希表的深拷贝),将新建的哈希表入哈希表栈。
-
-
如果遇到右括号,则是一层的结束,执行如下操作。
-
将表达式栈的栈顶表达式出栈,该表达式称为当前表达式,该表达式出栈之后位于栈顶的表达式称为栈顶表达式。
-
根据当前表达式的类型,执行相应的操作。
-
如果当前表达式的类型是 let 表达式,则获得当前表达式的最后一个部分,如果最后一个部分是变量则从当前层的哈希表(即哈希表栈的栈顶哈希表)中得到对应的整数并添加到栈顶表达式的末尾,如果最后一个部分是整数则将整数添加到栈顶表达式的末尾,最后将当前层的哈希表从哈希表栈出栈。
-
如果当前表达式的类型是 add 表达式或 mult 表达式,则取出当前表达式的两个运算数,执行相应的运算(add 表达式对应加法,mult 表达式对应乘法),将结果添加到栈顶表达式的末尾。
-
-
如果栈顶表达式的类型是 let 表达式且栈顶表达式的长度是奇数,则此时栈顶表达式的最后两个部分是变量名和整数,将变量名和整数的对应关系加入栈顶层的哈希表(即哈希表栈的栈顶哈希表)中。
-
-
如果遇到字母,则是一个变量的开始,执行如下操作。以下将表达式栈的栈顶表达式称为当前表达式,将哈希表栈的栈顶哈希表称为当前哈希表。
-
继续遍历后面的字符,直到遍历到空格或者右括号,此时得到变量名。
-
根据当前表达式的类型,执行相应的操作。
-
如果当前表达式的类型是 let 表达式,则判断表达式的长度(即表达式中已有的部分数量)的奇偶性。如果当前表达式的长度是偶数则当前部分需要填整数,因此从当前哈希表中得到当前变量对应的整数,将当前表达式的最后一个变量和整数的对应关系加入当前哈希表中,并将整数添加到当前表达式的末尾;如果当前表达式的长度是奇数则当前部分需要填变量,因此将变量添加到当前表达式的末尾。
-
如果当前表达式的类型是 add 表达式或 mult 表达式,则从当前哈希表中得到变量对应的整数,将整数添加到当前表达式的末尾。
-
-
-
如果遇到数字,则是一个整数的开始,执行如下操作。以下将表达式栈的栈顶表达式称为当前表达式,将哈希表栈的栈顶哈希表称为当前哈希表。
-
继续遍历后面的字符,直到遍历到空格或者右括号,此时得到整数。
-
如果当前表达式的类型是 let 表达式,则将当前表达式的最后一个变量和整数的对应关系加入当前哈希表中。
-
将整数添加到当前表达式的末尾。
-
考虑一个例子: expression = “(let x 2 y 5 (mult x (let x 4 z y (add x z))))" \textit{expression} = \text{``(let x 2 y 5 (mult x (let x 4 z y (add x z))))"} expression=“(let x 2 y 5 (mult x (let x 4 z y (add x z))))"。
初始时,将一个空表达式入表达式栈,将一个空哈希表入哈希表栈。遍历表达式的过程如下。
-
遍历到 (let \text{(let} (let,表达式栈为 [[], [let]] \text{[[], [let]]} [[], [let]],哈希表栈为 [[], []] \text{[[], []]} [[], []],其中左边为栈底,右边为栈顶。
-
遍历到 x \text{x} x,表达式栈为 [[], [let x]] \text{[[], [let x]]} [[], [let x]],哈希表栈为 [[], []] \text{[[], []]} [[], []]。
-
遍历到 2 2 2,表达式栈为 [[], [let x 2]] \text{[[], [let x 2]]} [[], [let x 2]],哈希表栈为 [[], [x=2]] \text{[[], [x=2]]} [[], [x=2]]。
-
遍历到 y \text{y} y,表达式栈为 [[], [let x 2 y]] \text{[[], [let x 2 y]]} [[], [let x 2 y]],哈希表栈为 [[], [x=2]] \text{[[], [x=2]]} [[], [x=2]]。
-
遍历到 5 5 5,表达式栈为 [[], [let x 2 y 5]] \text{[[], [let x 2 y 5]]} [[], [let x 2 y 5]],哈希表栈为 [[], [x=2, y=5]] \text{[[], [x=2, y=5]]} [[], [x=2, y=5]]。
-
遍历到 (mult \text{(mult} (mult,表达式栈为 [[], [let x 2 y 5], [mult]] \text{[[], [let x 2 y 5], [mult]]} [[], [let x 2 y 5], [mult]],哈希表栈为 [[], [x=2, y=5]] \text{[[], [x=2, y=5]]} [[], [x=2, y=5]]。
-
遍历到 x \text{x} x,表达式栈为 [[], [let x 2 y 5], [mult 2]] \text{[[], [let x 2 y 5], [mult 2]]} [[], [let x 2 y 5], [mult 2]],哈希表栈为 [[], [x=2, y=5]] \text{[[], [x=2, y=5]]} [[], [x=2, y=5]]。
-
遍历到 (let \text{(let} (let,表达式栈为 [[], [let x 2 y 5], [mult 2], [let]] \text{[[], [let x 2 y 5], [mult 2], [let]]} [[], [let x 2 y 5], [mult 2], [let]],哈希表栈为 [[], [x=2, y=5], [x=2, y=5]] \text{[[], [x=2, y=5], [x=2, y=5]]} [[], [x=2, y=5], [x=2, y=5]]。
-
遍历到 x \text{x} x,表达式栈为 [[], [let x 2 y 5], [mult 2], [let x]] \text{[[], [let x 2 y 5], [mult 2], [let x]]} [[], [let x 2 y 5], [mult 2], [let x]],哈希表栈为 [[], [x=2, y=5], [x=2, y=5]] \text{[[], [x=2, y=5], [x=2, y=5]]} [[], [x=2, y=5], [x=2, y=5]]。
-
遍历到 4 4 4,表达式栈为 [[], [let x 2 y 5], [mult 2], [let x 4]] \text{[[], [let x 2 y 5], [mult 2], [let x 4]]} [[], [let x 2 y 5], [mult 2], [let x 4]],哈希表栈为 [[], [x=2, y=5], [x=4, y=5]] \text{[[], [x=2, y=5], [x=4, y=5]]} [[], [x=2, y=5], [x=4, y=5]]。
-
遍历到 z \text{z} z,表达式栈为 [[], [let x 2 y 5], [mult 2], [let x 4 z]] \text{[[], [let x 2 y 5], [mult 2], [let x 4 z]]} [[], [let x 2 y 5], [mult 2], [let x 4 z]],哈希表栈为 [[], [x=2, y=5], [x=4, y=5]] \text{[[], [x=2, y=5], [x=4, y=5]]} [[], [x=2, y=5], [x=4, y=5]]。
-
遍历到 y \text{y} y,表达式栈为 [[], [let x 2 y 5], [mult 2], [let x 4 z 5]] \text{[[], [let x 2 y 5], [mult 2], [let x 4 z 5]]} [[], [let x 2 y 5], [mult 2], [let x 4 z 5]],哈希表栈为 [[], [x=2, y=5], [x=4, y=5, z=5]] \text{[[], [x=2, y=5], [x=4, y=5, z=5]]} [[], [x=2, y=5], [x=4, y=5, z=5]]。
-
遍历到 (add \text{(add} (add,表达式栈为 [[], [let x 2 y 5], [mult 2], [let x 4 z 5], [add]] \text{[[], [let x 2 y 5], [mult 2], [let x 4 z 5], [add]]} [[], [let x 2 y 5], [mult 2], [let x 4 z 5], [add]],哈希表栈为 [[], [x=2, y=5], [x=4, y=5, z=5]] \text{[[], [x=2, y=5], [x=4, y=5, z=5]]} [[], [x=2, y=5], [x=4, y=5, z=5]]。
-
遍历到 x \text{x} x,表达式栈为 [[], [let x 2 y 5], [mult 2], [let x 4 z 5], [add 4]] \text{[[], [let x 2 y 5], [mult 2], [let x 4 z 5], [add 4]]} [[], [let x 2 y 5], [mult 2], [let x 4 z 5], [add 4]],哈希表栈为 [[], [x=2, y=5], [x=4, y=5, z=5]] \text{[[], [x=2, y=5], [x=4, y=5, z=5]]} [[], [x=2, y=5], [x=4, y=5, z=5]]。
-
遍历到 z \text{z} z,表达式栈为 [[], [let x 2 y 5], [mult 2], [let x 4 z 5], [add 4 5]] \text{[[], [let x 2 y 5], [mult 2], [let x 4 z 5], [add 4 5]]} [[], [let x 2 y 5], [mult 2], [let x 4 z 5], [add 4 5]],哈希表栈为 [[], [x=2, y=5], [x=4, y=5, z=5]] \text{[[], [x=2, y=5], [x=4, y=5, z=5]]} [[], [x=2, y=5], [x=4, y=5, z=5]]。
-
遍历到 ) \text{)} ),表达式栈为 [[], [let x 2 y 5], [mult 2], [let x 4 z 5 9]] \text{[[], [let x 2 y 5], [mult 2], [let x 4 z 5 9]]} [[], [let x 2 y 5], [mult 2], [let x 4 z 5 9]],哈希表栈为 [[], [x=2, y=5], [x=4, y=5, z=5]] \text{[[], [x=2, y=5], [x=4, y=5, z=5]]} [[], [x=2, y=5], [x=4, y=5, z=5]]。
-
遍历到 ) \text{)} ),表达式栈为 [[], [let x 2 y 5], [mult 2 9]] \text{[[], [let x 2 y 5], [mult 2 9]]} [[], [let x 2 y 5], [mult 2 9]],哈希表栈为 [[], [x=2, y=5]] \text{[[], [x=2, y=5]]} [[], [x=2, y=5]]。
-
遍历到 ) \text{)} ),表达式栈为 [[], [let x 2 y 5 18]] \text{[[], [let x 2 y 5 18]]} [[], [let x 2 y 5 18]],哈希表栈为 [[], [x=2, y=5]] \text{[[], [x=2, y=5]]} [[], [x=2, y=5]]。
-
遍历到 ) \text{)} ),表达式栈为 [[18]] \text{[[18]]} [[18]],哈希表栈为 [[]] \text{[[]]} [[]]。
遍历结束,表达式栈内只剩下一个表达式,其值为 18 18 18,因此结果是 18 18 18。
代码
class Solution {
public int evaluate(String expression) {
Deque<Deque<String>> exprStack = new ArrayDeque<Deque<String>>();
exprStack.push(new ArrayDeque<String>());
Deque<Map<String, String>> mapStack = new ArrayDeque<Map<String, String>>();
mapStack.push(new HashMap<String, String>());
int length = expression.length();
int index = 0;
while (index < length) {
char c = expression.charAt(index);
if (c == '(') {
index++;
StringBuffer temp = new StringBuffer();
while (expression.charAt(index) != ' ') {
temp.append(expression.charAt(index));
index++;
}
String type = temp.toString();
Deque<String> currExpr = new ArrayDeque<String>();
currExpr.offerFirst(type);
exprStack.push(currExpr);
if ("let".equals(type)) {
mapStack.push(new HashMap<String, String>(mapStack.peek()));
}
} else {
Deque<String> currExpr = exprStack.peek();
Map<String, String> currMap = mapStack.peek();
if (c == ')') {
exprStack.pop();
Deque<String> topExpr = exprStack.peek();
String type = currExpr.peekFirst();
if ("let".equals(type)) {
String str = currExpr.peekLast();
if (Character.isLetter(str.charAt(0))) {
topExpr.offerLast(currMap.get(str));
} else {
topExpr.offerLast(str);
}
mapStack.pop();
} else {
currExpr.pollFirst();
int num1 = Integer.parseInt(currExpr.pollFirst());
int num2 = Integer.parseInt(currExpr.pollFirst());
int result = "add".equals(type) ? num1 + num2 : num1 * num2;
topExpr.offerLast(Integer.toString(result));
}
String topType = topExpr.peekFirst();
if ("let".equals(topType) && topExpr.size() % 2 != 0) {
String lastStr = topExpr.pollLast();
currMap = mapStack.peek();
currMap.put(topExpr.peekLast(), lastStr);
topExpr.offerLast(lastStr);
}
index++;
} else if (Character.isLetter(c)) {
StringBuffer temp = new StringBuffer();
while (expression.charAt(index) != ' ' && expression.charAt(index) != ')') {
temp.append(expression.charAt(index));
index++;
}
String str = temp.toString();
String type = currExpr.peekFirst();
if ("let".equals(type)) {
if (currExpr.size() % 2 == 0) {
String variable = currExpr.peekLast();
currMap.put(variable, currMap.get(str));
currExpr.offerLast(currMap.get(str));
} else {
currExpr.offerLast(str);
}
} else {
currExpr.offerLast(currMap.get(str));
}
} else {
StringBuffer temp = new StringBuffer();
while (expression.charAt(index) != ' ' && expression.charAt(index) != ')') {
temp.append(expression.charAt(index));
index++;
}
String str = temp.toString();
String type = currExpr.peekFirst();
if ("let".equals(type)) {
String variable = currExpr.peekLast();
currMap.put(variable, str);
}
currExpr.offerLast(str);
}
}
if (index < length && expression.charAt(index) == ' ') {
index++;
}
}
return Integer.parseInt(exprStack.pop().peekFirst());
}
}
复杂度分析
-
时间复杂度: O ( n 2 ) O(n^2) O(n2),其中 n n n 是字符串 expression \textit{expression} expression 的长度。两个栈内最多有 O ( n ) O(n) O(n) 层,每次入栈和出栈需要 O ( n ) O(n) O(n) 的时间,因此计算表达式的值需要 O ( n 2 ) O(n^2) O(n2) 的时间。
-
空间复杂度: O ( n 2 ) O(n^2) O(n2),其中 n n n 是字符串 expression \textit{expression} expression 的长度。空间复杂度取决于两个栈,由于哈希表栈内的每一层都需要存储上一层的变量和整数的对应关系,因此空间复杂度是 O ( n 2 ) O(n^2) O(n2)。