2019-解出数学表达式的学生分数
给你一个字符串 s
,它 只 包含数字 0-9
,加法运算符 '+'
和乘法运算符 '*'
,这个字符串表示一个 合法
的只含有 个位数 数字 的数学表达式(比方说 3+5*2
)。有 n
位小学生将计算这个数学表达式,并遵循如下 运算顺序
:
- 按照 从左到右 的顺序计算 乘法 ,然后
- 按照 从左到右 的顺序计算 加法 。
给你一个长度为 n
的整数数组 answers
,表示每位学生提交的答案。你的任务是给 answer
数组按照如下 规则 打分:
- 如果一位学生的答案 等于 表达式的正确结果,这位学生将得到
5
分。 - 否则,如果答案由 一处或多处错误的运算顺序 计算得到,那么这位学生能得到
2
分。 - 否则,这位学生将得到
0
分。
请你返回所有学生的分数和。
示例 1:
**输入:** s = "7+3*1*2", answers = [20,13,42]
**输出:** 7
**解释:** 如上图所示,正确答案为 13 ,因此有一位学生得分为 5 分:[20, _ **13**_ ,42] 。
一位学生可能通过错误的运算顺序得到结果 20 :7+3=10,10*1=10,10*2=20 。所以这位学生得分为 2 分:[ _ **20**_ ,13,42] 。
所有学生得分分别为:[2,5,0] 。所有得分之和为 2+5+0=7 。
示例 2:
**输入:** s = "3+5*2", answers = [13,0,10,13,13,16,16]
**输出:** 19
**解释:** 表达式的正确结果为 13 ,所以有 3 位学生得到 5 分:[ _ **13**_ ,0,10, _ **13**_ , _ **13**_ ,16,16] 。
学生可能通过错误的运算顺序得到结果 16 :3+5=8,8*2=16 。所以两位学生得到 2 分:[13,0,10,13,13, _ **16**_ , _ **16**_ ] 。
所有学生得分分别为:[5,0,0,5,5,2,2] 。所有得分之和为 5+0+0+5+5+2+2=19 。
示例 3:
**输入:** s = "6+0*1", answers = [12,9,6,4,8,6]
**输出:** 10
**解释:** 表达式的正确结果为 6 。
如果一位学生通过错误的运算顺序计算该表达式,结果仍为 6 。
根据打分规则,运算顺序错误的学生也将得到 5 分(因为他们仍然得到了正确的结果),而不是 2 分。
所有学生得分分别为:[0,0,5,0,0,5] 。所有得分之和为 10 分。
提示:
3 <= s.length <= 31
s
表示一个只包含0-9
,'+'
和'*'
的合法表达式。- 表达式中所有整数运算数字都在闭区间
[0, 9]
以内。 1 <=
数学表达式中所有运算符数目('+'
和'*'
)<= 15
- 测试数据保证正确表达式结果在范围
[0, 1000]
以内。 n == answers.length
1 <= n <= 104
0 <= answers[i] <= 1000
思路
这个题难点在于对运算顺序自由组合时,不知道怎么做组合。
其实可以简单的把一个表达式,拆分成总表达式 = (左侧表达式) 运算符 (右侧表达式)
的形式,先计算左侧表达式所有可能结果,再计算右侧表达式所有可能结果,再两两组合得到总表达式的所有结果。
具体操作时,对于一个总表达式,枚举它内部所有的运算符位置,然后每次计算左和右。可以观察到,左和右表达式显然长度≤总表达式长度,不难想到应该用 dp 来解。
具体代码时,使用区间 dp 的方法,使用一个二维数组 dp[i][j] 表示字符串 s[i..j] 总共可以通过调换计算顺序,得到哪些计算结果。
如此一来,假设我们知道了两个区间结果 dp[i][k] 和 dp[k][j],那我们就可以通过
dp[i][j]={x ○ y | x \in dp[i][k] \ and \ y \in dp[k][j]
来一步步从小区间扩大到大区间。
Step 1:统计回答
思路
因为错误的计算结果可能有很多种,所以我们先对所有学生提交的结果做一个简单统计,后续就不需要每次都 O(N) 遍历了。
代码及注释
1 | // 所有学生答案都在[0, 1000],因此开一个差不多大小的空间即可 |
Step 2:计算正确结果
思路
使用加法入栈,乘法直接将栈顶元素做乘法的方法,计算正确结果。
解释
顺序遍历时
- 对于
a + b ...
,由于不知道b
之后是否涉及到乘法运算,因此不可以直接将a
与b
相加,而是应该暂时把两个数都放在栈中; - 对于
a * b ...
,乘法运算优先级最高,因此此处直接计算a * b
的值,并更新栈顶的a
的值为a * b
代码及注释
1 | stack<int> st; |
Step 3:枚举所有可能结果
思路
这一步中,使用区间 dp 的方法,使用一个二维数组 dp[i][j] 表示字符串 s[i..j] 总共可以通过调换计算顺序,得到哪些计算结果,dp[i][j] 是一个集合。
初始化
初始条件时,是 i=j,此时 dp[i][j] = {s[i] - ‘0’
递推关系
对于一个区间 dp[i][j],我们有 dp[i][j] = {dp[i][k] ○ dp[k][j] \ 其中 i≤k≤j,符号 ○
表示加号或乘号关系。
额外Tips
这里需要引入一个剪枝,否则会因为爆 int
而出错。就是如果中间的某个计算结果已经超过 1000 了,应该直接忽略。
这是因为无论是加法还是乘法,都是一个不减的操作,当计算结果已经超过 1000 时,无论如何组合,则最终表达式的值一定不在学生猜测范围 [0,1000] 内,因此可以忽略。
代码及注释
1 | // 开空间,dp为n*n的数组,每一项为一个集合 |
Step 4:统计所有错误结果
思路
上一步计算的 dp[0][len-1]
就是整个字符串,所有可能的计算结果。结合第一步统计的学生答案,做累加
代码及注释
1 | for(auto p : dp[0][len - 1]){ |
Step 5:完整代码及注释
1 | class Solution { |