有LeetCode算法/华为OD考试扣扣交流群可加 948025485
可上全网独家的 欧弟OJ系统 练习华子OD、大厂真题
绿色聊天软件戳od1441
了解算法冲刺训练(备注【CSDN】否则不通过)
文章目录
从2024年4月15号开始,OD机考全部配置为2024D卷。
注意两个关键点:
- 会遇到C卷复用题。虽然可能存在幸存者偏差,但肯定还会有一大部分的旧题。
- 现在又支持做完题目之后倒回去改了。就是可以先做200的再做100的,然后可以反复提交。
题目描述与示例
题目描述
提取字符串中的最长合法简单数学表达式,字符串长度最长的,并计算表达式的值。如果没有,则返回0
。
简单数学表达式只能包含以下内容:0-9
数字,符号 +-*
说明:
- 所有数字,计算结果都不超过
long
- 如果有多个长度一样的,请返回第一个表达式的结果
- 数学表达式,必须是最长的,合法的
- 操作符不能连续出现,如
+--+1
是不合法的
输入描述
字符串
输出描述
表达式值
示例
输入
1+2abcd
输出
-1
解题思路
本题的难点其实在于如何提取出最长合法数学表达式,而不是最终的运算。
运算这一步可以直接使用eval()
内置函数来完成,也可以正常地使用栈来进行表达式求值
因为提取这一步本身已经比较复杂了,所以在得到最长合法数学表达式之后,求值使用
eval()
直接得到结果并没有太大问题。
提取合法表达式
什么是合法表达式
关于合法表达式的判断,题目其实并没有过多详细说明。
除了最基本的不能包括字母或其他字符,不能出现连续的操作符等等,我们需要特别注意以下情况:
- 单个数字属于合法表达式
"+"
和"-"
是可以作为符号出现在表达式的首位的,但是"*"
不行- 除了数字
0
以外,表达式中的任何一个数字都不能出现先导0
- 表达式的最后一位必须是数字,而不能是操作数
举一些例子就明白了。譬如
"123"
、"0"
、"-123"
、"-0"
、"+123"
、"+0"
是合法表达式"+1+2"
、"-1*2+3"
是合法表达式,但是"*1+2"
不是合法表达式"00"
、"01"
、"-00"
、"-01"
、"1-02"
不是合法表达式"+1+2+"
、"1*2+3-"
不是合法表达式
因此,关于合法表达式的提取,需要进行非常多的分类讨论。
合法表达式双指针框架
我们可以使用两个指针i
和j
来进行合法表达式的提取。
其中,i
表示合法表达式的起始位置,j
表示合法表达式的终止位置。譬如
abcd1-2abcd3+4*5
↑ ↑
i j
对于任何一个合法表达式(无论是否最长),我们就能够使用s[i:j]
来表示这个表达式。
显然区间[i, j)
是一个左闭右开区间,这样做的好处是,当考虑完一个合法表达式之后,我们都可以进一步地以j
的位置作为新的i
,来考虑后续的新的表达式。
另外,由于i
和j
的修改条件相对复杂,很难使用for
循环来完成,因此很容易想到关于i
和j
的前进都应该用while
循环来进行。
当s[i]
是一个数字,或者s[i]
是"+"
或"-"
的时候,i
可以作为一个合法表达式的起始位置。
在固定s[i]
为首位的情况下,我们可以初始化j = i
,让j
继续前进,寻找当前合法表达式的终止位置。
在结束关于j
的循环之后,我们再重置i = j
,作为下一个合法表达式寻找的起始位置。
当然,如果作为起始位置的s[i]
是其他无关字符("*"
或其他字母等等),则s[i]
不是一个合法的起始位置,直接令i
前进递增即可。
综上所述,我们可以构建出整体的双指针框架为
i = 0
while i < n:
# 关于表达式首位s[i]的判断
# 如果s[i]是数字,或者s[i]是"+"或"-"
# 则s[i]可以作为一个表达式的起始位置,继续后面的遍历过程
if s[i].isdigit() or s[i] in "+-":
j = i
while j < n:
pass
pass
i = j
# 如果s[i]既不是数字,也不是"+"或"-"
# 则s[i]不能作为一个表达式的起始位置
# i直接递增
else:
i += 1
关于j
的内层while
循环的判断
关于j
的内层循环,可以简单分为三种情况:
s[j]
是数字s[j]
是操作符"+-*"
s[j]
是其他无关字符
s[j]
是其他无关字符的情况
如果s[j]
是其他无关字符,这种情况最为简单,说明当前s[i:j]
就是一个无法继续延长的合法表达式,可以直接退出关于j
的while
循环。譬如
abcd1-2abcd3+4*5
↑ ↑
i j
故框架可以修改如下
i = 0
while i < n:
# 关于表达式首位s[i]的判断
# 如果s[i]是数字,或者s[i]是"+"或"-"
# 则s[i]可以作为一个表达式的起始位置,继续后面的遍历过程
if s[i].isdigit() or s[i] in "+-":
j = i
while j < n:
# s[j]是数字
if s[j].isdigit():
pass
# s[j]是运算符号
elif s[j] in "+-*":
pass
else:
break
pass
i = j
# 如果s[i]既不是数字,也不是"+"或"-"
# 则s[i]不能作为一个表达式的起始位置
# i直接递增
else:
i += 1
注意到,这里的break
即表示退出循环时,j
所代表的终止位置必须表示开区间取不到的位置。
这一点在后续的讨论中非常重要,如果不注意这一点的话很容易使得最后取得的表达式长1
位或短1
位。
s[j]
是数字的情况
当s[j]
是1-9
之间的数字时,这种情况也是比较简单的。
此时不用考虑任何特殊情况,此时的s[j]
一定是表达式中的一部分,直接进行j
的递增,考虑下一个j
即可。譬如
abcd1-2+34*56abcd3+4*5
↑ ↑
i j
对应的代码为
# s[j]是数字
if s[j].isdigit():
# s[j]是1-9
if 1 <= int(s[j]) <= 9:
j += 1
pass
当s[j]
是0
的时候,情况就变得复杂了。
因为可能存在非法先导0
,如果s[j]
是一个非法先导0
的话,那么此时表达式必须在s[j+1]
的位置结束,应该令j
递增1
之后,退出循环。
譬如"1+02"
,那么应该取"0"
之后的下一个字符字符"2"
应该作为终止位置,提取出合法表达式"1+0"
。
abcd1+02abcd3+4*5
↑ ↑
i j
非法先导0
必须同时满足两个条件:
- 非法先导
0
的前一位必须是一个非数字(可以是操作符、也可以是其他无关字符),这意味着这个0
并不是位于某个数字中其他位置。譬如,"101"
和"110"
中的0
就不是先导0
。 - 非法先导
0
的后一位必须是一个数字,这意味着这个0
并非一个单独的0
(因为对于数字0
而言,先导0
是合法的),而是后面跟着数字。譬如,"1+0+1"
和"1+1+0"
中的0
就是合法的。
如果s[j]
不是一个非法先导0
的话,那么这个0
的行为就可以其他数字的行为一致,直接递增即可。
因此,关于先导0
的判断,我们可以进一步填充上述代码
# s[j]是数字
if s[j].isdigit():
# s[j]是1-9
if 1 <= int(s[j]) <= 9:
j += 1
# s[j]是0
else:
# 需要判断这个0是否是一个先导0
# 如果s[j-1]不是数字,且s[j+1]是数字,说明s[j]这个0是一个先导0
# 此时表达式只能取到该0为止
# 退出循环
if not s[j-1].isdigit() and s[j+1].isdigit():
j += 1
break
# 如果这个0的前面的s[j-1]是数字,比如10,0前面的1
# 或者这个0的后面的s[j+1]不是数字,比如0+1,0后面的+
# 那么这个0不是先导0,
# 它的行为和其他数字的行为一致,j正常地进行递增操作,无需退出循环
else:
j += 1
s[j]
是操作符的情况
当s[j]
是一个操作符的时候,只有当s[j]
的后面紧跟着一个数字时,这个表达式才能进一步延长。
否则,此时表达式必须在s[j]
的位置结束,譬如"1+2+"
,我们要提取出合法表达式"1+2"
。即
abcd1+2+abcd3+4*5
↑ ↑
i j
对应代码为
# s[j]是运算符号
elif s[j] in "+-*":
# 如果s[j+1]是一个数字,则表达式合法,j可以进一步递增
if s[j+1].isdigit():
j += 1
# 否则,表达式只到当前s[j]结束,当前的合法表达式为s[i:j],退出循环
else:
break
将三种情况均讨论完毕后,关于j
的while
循环内的内容也就完成了。我们更新代码框架如下
i = 0
while i < n:
# 关于表达式首位s[i]的判断
# 如果s[i]是数字,或者s[i]是"+"或"-"
# 则s[i]可以作为一个表达式的起始位置,继续后面的遍历过程
if s[i].isdigit() or s[i] in "+-":
j = i
while j < n:
# s[j]是数字
if s[j].isdigit():
pass
# s[j]是运算符号
elif s[j] in "+-*":
if s[j+1].isdigit():
j += 1
else:
break
else:
break
pass
i = j
# 如果s[i]既不是数字,也不是"+"或"-"
# 则s[i]不能作为一个表达式的起始位置
# i直接递增
else:
i += 1
退出内层循环后的答案更新
再次重申,退出关于j
的while
循环后,此时的表达式为s[i:j]
,j
表示右开区间。
在关于j
的while
循环中,单个"+"
或"-"
并不会被认为是一个合法表达式。
譬如"++1"
或"+a1"
中的第一个"+"
,并不会被认为是一个合法表达式。
此时存在i == j
成立,s[i:j]
是一个空串。
由于后续我们需要更新i = j
,重新以j
作为一个新的表达式起始位置,如果此时不对j
做任何修改,那么i
会始终停留在当前位置,从而导致死循环。
故如果出现i == j
成立,我们必须强制令j
前进一位。
while j < n:
pass
if i == j:
j += 1
pass
i = j
剩下的就是答案更新了,由于题目要求找到最长合法表达式,我们可以使用两个全局变量start_idx
和end_idx
来储存全局的最长合法表达式的起始位置和终止位置。
初始化start_idx
和end_idx
相等(即它们做差为0
),当发现j - i > start_idx - end_idx
的时候,我们将start_idx
和end_idx
分别修改为j
和i
即可。
while j < n:
pass
if i == j:
j += 1
elif j - i > end_idx - start_idx:
start_idx, end_idx = i, j
i = j
这里的判断条件之所以是j - i > end_idx - start_idx
而不是j - i >= end_idx - start_idx
,是因为题目要求当出现多个最长合法表达式时,选择第一个最长合法表达式。
>
可以保证,只有在找到更长的表达式的时候才进行更新。
如果题目要求当出现多个最长合法表达式时,选择最后一个最长合法表达式的话,那么应该使用
>=
。这样就可以在找到等长的最长表达式时,也进行更新了,最终结果一定是最后一个最长合法表达式。
在退出关于i
的while
循环之后,全局的start_idx
和end_idx
也更新完毕了。
sub_s = s[start_idx:end_idx]
就是在双指针循环中拿到的最长合法表达式。
如果sub_s
是空串(即原字符串中不存在任何合法表达式),则直接输出0
。
否则直接调用内置函数eval()
得到结果。
sub_s = s[start_idx:end_idx]
print(eval(sub_s) if sub_s else 0)
避免越界的技巧
在上述关于j
的while
循环中,我们的代码其实并没有讨论关于j
的越界情况。
实际上在循环中是有可能出现越界的,因为我们在循环中多次取了s[j-1]
和s[j+1]
来进行判断。
但因为分类讨论的情况很多,比较复杂,如果反复地进行越界判断会使得代码可读性变得非常差,也不利于我们进行调试和修改。
在最终的代码中,我们用上了这样一个技巧:在循环开始之前,往原字符串s
的前后均填充了一个不会影响最终结果的字符。
s = "a" + s + "a"
无论原来的s
的最开头和最末尾是什么字符,新填充的两个无关字符必然不会被包含在表达式中,那么在关于j
的while
循环中,我们在取s[j-1]
和s[j+1]
的时候,必然不会出现越界,也不需要在while
循环中写上繁琐的越界判断了。
栈模拟表达式求值
在提取出最长合法表达式之后,剩下的内容就比较常规了,使用栈来进行表达式求值的模拟。
对于使用python的同学而言,这里可以直接用eval()
内置函数得到结果,但其他语言的同学则需要手动实现eval()
功能。
# 计算表达式的函数cal()
def cal(s):
# s末尾添加一个空字符或随意一个操作符
# 用以避免边界情况的额外讨论
s += " "
# 初始化前一个操作符pre_sign为"+"
pre_sign = "+"
stack = list()
num = 0
for ch in s:
# 遇到数字,更新num
if ch.isdigit():
num = num * 10 + int(ch)
# 遇到操作符:
# 1. 储存num
# 2. 如果前面是一个乘法,需要进行计算
else:
# 如果前一个操作符pre_sign是"-",则储存-num
if pre_sign == "-":
stack.append(-num)
# 如果前一个操作符pre_sign是"+"或"*",则储存num
else:
stack.append(num)
num = 0
# 如果前一个操作符pre_sign是"*",则高优先级的乘法需要先计算
# 弹出栈顶两个数字,进行相乘
if pre_sign == "*":
stack.append(stack.pop() * stack.pop())
# 修改pre_sign为ch,表示下一个表达式的前一个符号是当前符号
pre_sign = ch
# 返回栈中元素和即为最后的加减法操作得到的结果
return sum(stack)
相关的题目包括LC224. 基本计算器、LeetCode227. 基本计算器II、【栈】2023C-火星文计算2
代码
python
代码一:直接使用eval()内置函数
# 题目:【双指针】2024D-提取字符串中最长数学表达式
# 分值:100
# 作者:许老师-闭着眼睛学数理化
# 算法:双指针模拟/栈
# 代码看不懂的地方,请直接在群上提问
s = input()
# 往原字符串s的前后插入两个无关字母"a"
# 方便后续关于边界的判断,但不会影响最终结果
s = "a" + s + "a"
# 初始化最长表达式的起始索引和终止索引
start_idx, end_idx = -1, -1
n = len(s)
# 初始化指针i,在后续循环中,表示一个合法表达式的起始位置
i = 0
while i < n:
# 关于表达式首位s[i]的判断
# 如果s[i]是数字,或者s[i]是"+"或"-"
# 则s[i]可以作为一个表达式的起始位置,继续后面的遍历过程
if s[i].isdigit() or s[i] in "+-":
j = i
while j < n:
# s[j]是数字
if s[j].isdigit():
# s[j]是1-9
if 1 <= int(s[j]) <= 9:
j += 1
# s[j]是0
else:
# 需要判断这个0是否是一个先导0
# 如果s[j-1]不是数字,且s[j+1]是数字,说明s[j]这个0是一个先导0
# 此时表达式只能取到该0为止
# 退出循环
if not s[j-1].isdigit() and s[j+1].isdigit():
j += 1
break
# 如果这个0的前面s[j-1]是数字,比如0
# 或者这个0的后面s[j+1]不是数字,比如0+
# 那么这个0不是先导0,
# 它的行为和其他数字的行为一致,j正常地进行递增操作,无需退出循环
else:
j += 1
# s[j]是运算符号
elif s[j] in "+-*":
# 如果s[j+1]是一个数字,则表达式合法,j可以进一步递增
if s[j+1].isdigit():
j += 1
# 否则,表达式只到当前s[j]结束,当前的合法表达式为s[i:j],退出循环
else:
break
# s[j]是其他无关字符
# 表达式只到当前s[j]结束,当前的合法表达式为s[i:j],退出循环
else:
break
# 如果上述关于while循环结束后,仍存在i == j
# 只可能是一种情况:
# 当前s[i] == s[j]为单个的"+"或"-"
# 且s[i+1] == s[j+1]的后面不是一个数字
# 此时表达式不存在,为空串,j递增
if i == j:
j += 1
# 如果当前表达式存在,且长度大于之前储存的表达式的最大长度
# 则储存表达式的起始索引start_idx为i,终止索引end_idx为j
elif j - i > end_idx - start_idx:
start_idx, end_idx = i, j
# 将i设置为j,为下一个开始考虑的位置
i = j
# 如果s[i]既不是数字,也不是"+"或"-"
# 则s[i]不能作为一个表达式的起始位置
# i直接递增
else:
i += 1
# 根据索引start_idx和end_idx来获得最终的最长合法表达式sub_s
sub_s = s[start_idx:end_idx]
# 如果最终sub_s不是一个空串,则可以进行表达式求值运算
# 可以直接使用eval()函数,也可以用栈来进行运算
print(eval(sub_s) if sub_s else 0)
代码二:栈模拟表达式求值
# 题目:【双指针】2024D-提取字符串中最长数学表达式
# 分值:100
# 作者:许老师-闭着眼睛学数理化
# 算法:双指针模拟/栈
# 代码看不懂的地方,请直接在群上提问
# 计算表达式的函数cal()
def cal(s):
# s末尾添加一个空字符或随意一个操作符
# 用以避免边界情况的额外讨论
s += " "
# 初始化前一个操作符pre_sign为"+"
pre_sign = "+"
stack = list()
num = 0
for ch in s:
# 遇到数字,更新num
if ch.isdigit():
num = num * 10 + int(ch)
# 遇到操作符:
# 1. 储存num
# 2. 如果前面是一个乘法,需要进行计算
else:
# 如果前一个操作符pre_sign是"-",则储存-num
if pre_sign == "-":
stack.append(-num)
# 如果前一个操作符pre_sign是"+"或"*",则储存num
else:
stack.append(num)
num = 0
# 如果前一个操作符pre_sign是"*",则高优先级的乘法需要先计算
# 弹出栈顶两个数字,进行相乘
if pre_sign == "*":
stack.append(stack.pop() * stack.pop())
# 修改pre_sign为ch,表示下一个表达式的前一个符号是当前符号
pre_sign = ch
# 返回栈中元素和即为最后的加减法操作得到的结果
return sum(stack)
s = input()
# 往原字符串s的前后插入两个无关字母"a"
# 方便后续关于边界的判断,但不会影响最终结果
s = "a" + s + "a"
# 初始化最长表达式的起始索引和终止索引
start_idx, end_idx = -1, -1
n = len(s)
# 初始化指针i,在后续循环中,表示一个合法表达式的起始位置
i = 0
while i < n:
# 关于表达式首位s[i]的判断
# 如果s[i]是数字,或者s[i]是"+"或"-"
# 则s[i]可以作为一个表达式的起始位置,继续后面的遍历过程
if s[i].isdigit() or s[i] in "+-":
j = i
while j < n:
# s[j]是数字
if s[j].isdigit():
# s[j]是1-9
if 1 <= int(s[j]) <= 9:
j += 1
# s[j]是0
else:
# 需要判断这个0是否是一个先导0
# 如果s[j-1]不是数字,且s[j+1]是数字,说明s[j]这个0是一个先导0
# 此时表达式只能取到该0为止
# 退出循环
if not s[j-1].isdigit() and s[j+1].isdigit():
j += 1
break
# 如果这个0的前面s[j-1]是数字,比如0
# 或者这个0的后面s[j+1]不是数字,比如0+
# 那么这个0不是先导0,
# 它的行为和其他数字的行为一致,j正常地进行递增操作,无需退出循环
else:
j += 1
# s[j]是运算符号
elif s[j] in "+-*":
# 如果s[j+1]是一个数字,则表达式合法,j可以进一步递增
if s[j+1].isdigit():
j += 1
# 否则,表达式只到当前s[j]结束,当前的合法表达式为s[i:j],退出循环
else:
break
# s[j]是其他无关字符
# 表达式只到当前s[j]结束,当前的合法表达式为s[i:j],退出循环
else:
break
# 如果上述关于while循环结束后,仍存在i == j
# 只可能是一种情况:
# 当前s[i] == s[j]为单个的"+"或"-"
# 且s[i+1] == s[j+1]的后面不是一个数字
# 此时表达式不存在,为空串,j递增
if i == j:
j += 1
# 如果当前表达式存在,且长度大于之前储存的表达式的最大长度
# 则储存表达式的起始索引start_idx为i,终止索引end_idx为j
elif j - i > end_idx - start_idx :
start_idx, end_idx = i, j
# 将i设置为j,为下一个开始考虑的位置
i = j
# 如果s[i]既不是数字,也不是"+"或"-"
# 则s[i]不能作为一个表达式的起始位置
# i直接递增
else:
i += 1
# 根据索引start_idx和end_idx来获得最终的最长合法表达式sub_s
sub_s = s[start_idx:end_idx]
# 如果最终sub_s不是一个空串,则可以进行表达式求值运算
# 可以直接使用eval()函数,也可以用栈来进行运算
print(cal(sub_s) if sub_s else 0)
java
import java.util.Scanner;
import java.util.Stack;
public class Main {
// 计算表达式的函数cal()
public static int cal(String s) {
// s末尾添加一个空字符或随意一个操作符
// 用以避免边界情况的额外讨论
String expr = s + " ";
// 初始化前一个操作符pre_sign为"+"
char pre_sign = '+';
Stack<Integer> stk = new Stack<>();
int num = 0;
for (char ch : expr.toCharArray()) {
// 遇到数字,更新num
if (Character.isDigit(ch)) {
num = num * 10 + (ch - '0');
}
// 遇到操作符:
// 1. 储存num
// 2. 如果前面是一个乘法,需要进行计算
else {
// 如果前一个操作符pre_sign是"-",则储存-num
if (pre_sign == '-') {
stk.push(-num);
}
// 如果前一个操作符pre_sign是"+"或"*",则储存num
else {
stk.push(num);
}
num = 0;
// 如果前一个操作符pre_sign是"*",则高优先级的乘法需要先计算
// 弹出栈顶两个数字,进行相乘
if (pre_sign == '*') {
int a = stk.pop();
int b = stk.pop();
stk.push(a * b);
}
// 修改pre_sign为ch,表示下一个表达式的前一个符号是当前符号
pre_sign = ch;
}
}
// 返回栈中元素和即为最后的加减法操作得到的结果
int result = 0;
while (!stk.isEmpty()) {
result += stk.pop();
}
return result;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String s = sc.nextLine();
// 往原字符串s的前后插入两个无关字母"a"
// 方便后续关于边界的判断,但不会影响最终结果
s = "a" + s + "a";
// 初始化最长表达式的起始索引和终止索引
int start_idx = -1, end_idx = -1;
int n = s.length();
// 初始化指针i,在后续循环中,表示一个合法表达式的起始位置
int i = 0;
while (i < n) {
// 关于表达式首位s[i]的判断
// 如果s[i]是数字,或者s[i]是"+"或"-"
// 则s[i]可以作为一个表达式的起始位置,继续后面的遍历过程
if (Character.isDigit(s.charAt(i)) || s.charAt(i) == '+' || s.charAt(i) == '-') {
int j = i;
while (j < n) {
// s[j]是数字
if (Character.isDigit(s.charAt(j))) {
// s[j]是1-9
if (s.charAt(j) != '0') {
j++;
}
// s[j]是0
else {
// 需要判断这个0是否是一个先导0
// 如果s[j-1]不是数字,且s[j+1]是数字,说明s[j]这个0是一个先导0
// 此时表达式只能取到该0为止
// 退出循环
if (!Character.isDigit(s.charAt(j - 1)) && Character.isDigit(s.charAt(j + 1))) {
j++;
break;
}
// 如果这个0的前面s[j-1]是数字,比如0
// 或者这个0的后面s[j+1]不是数字,比如0+
// 那么这个0不是先导0,
// 它的行为和其他数字的行为一致,j正常地进行递增操作,无需退出循环
else {
j++;
}
}
}
// s[j]是运算符号
else if (s.charAt(j) == '+' || s.charAt(j) == '-' || s.charAt(j) == '*') {
// 如果s[j+1]是一个数字,则表达式合法,j可以进一步递增
if (Character.isDigit(s.charAt(j + 1))) {
j++;
}
// 否则,表达式只到当前s[j]结束,当前的合法表达式为s[i:j],退出循环
else {
break;
}
}
// s[j]是其他无关字符
// 表达式只到当前s[j]结束,当前的合法表达式为s[i:j],退出循环
else {
break;
}
}
// 如果上述关于while循环结束后,仍存在i == j
// 只可能是一种情况:
// 当前s[i] == s[j]为单个的"+"或"-"
// 且s[i+1] == s[j+1]的后面不是一个数字
// 此时表达式不存在,为空串,j递增
if (i == j) {
j++;
}
// 如果当前表达式存在,且长度大于之前储存的表达式的最大长度
// 则储存表达式的起始索引start_idx为i,终止索引end_idx为j
else if (j - i > end_idx - start_idx) {
start_idx = i;
end_idx = j;
}
// 将i设置为j,为下一个开始考虑的位置
i = j;
}
// 如果s[i]既不是数字,也不是"+"或"-"
// 则s[i]不能作为一个表达式的起始位置
// i直接递增
else {
i++;
}
}
// 如果最终sub_s不是一个空串,则可以进行表达式求值运算
if (start_idx < end_idx) {
// 根据索引start_idx和end_idx来获得最终的最长合法表达式sub_s
String sub_s = s.substring(start_idx, end_idx);
System.out.println(cal(sub_s));
} else {
System.out.println(0);
}
sc.close();
}
}
cpp
#include <iostream>
#include <vector>
#include <stack>
#include <string>
#include <cctype>
using namespace std;
// 计算表达式的函数cal()
int cal(const string& s) {
// s末尾添加一个空字符或随意一个操作符
// 用以避免边界情况的额外讨论
string expr = s + " ";
// 初始化前一个操作符pre_sign为"+"
char pre_sign = '+';
stack<int> stk;
int num = 0;
for (char ch : expr) {
// 遇到数字,更新num
if (isdigit(ch)) {
num = num * 10 + (ch - '0');
}
// 遇到操作符:
// 1. 储存num
// 2. 如果前面是一个乘法,需要进行计算
else {
// 如果前一个操作符pre_sign是"-",则储存-num
if (pre_sign == '-') {
stk.push(-num);
}
// 如果前一个操作符pre_sign是"+"或"*",则储存num
else {
stk.push(num);
}
num = 0;
// 如果前一个操作符pre_sign是"*",则高优先级的乘法需要先计算
// 弹出栈顶两个数字,进行相乘
if (pre_sign == '*') {
int a = stk.top(); stk.pop();
int b = stk.top(); stk.pop();
stk.push(a * b);
}
// 修改pre_sign为ch,表示下一个表达式的前一个符号是当前符号
pre_sign = ch;
}
}
// 返回栈中元素和即为最后的加减法操作得到的结果
int result = 0;
while (!stk.empty()) {
result += stk.top();
stk.pop();
}
return result;
}
int main() {
string s;
cin >> s;
// 往原字符串s的前后插入两个无关字母"a"
// 方便后续关于边界的判断,但不会影响最终结果
s = "a" + s + "a";
// 初始化最长表达式的起始索引和终止索引
int start_idx = -1, end_idx = -1;
int n = s.length();
// 初始化指针i,在后续循环中,表示一个合法表达式的起始位置
int i = 0;
while (i < n) {
// 关于表达式首位s[i]的判断
// 如果s[i]是数字,或者s[i]是"+"或"-"
// 则s[i]可以作为一个表达式的起始位置,继续后面的遍历过程
if (isdigit(s[i]) || s[i] == '+' || s[i] == '-') {
int j = i;
while (j < n) {
// s[j]是数字
if (isdigit(s[j])) {
// s[j]是1-9
if (s[j] != '0') {
j++;
}
// s[j]是0
else {
// 需要判断这个0是否是一个先导0
// 如果s[j-1]不是数字,且s[j+1]是数字,说明s[j]这个0是一个先导0
// 此时表达式只能取到该0为止
// 退出循环
if (!isdigit(s[j-1]) && isdigit(s[j+1])) {
j++;
break;
}
// 如果这个0的前面s[j-1]是数字,比如0
// 或者这个0的后面s[j+1]不是数字,比如0+
// 那么这个0不是先导0,
// 它的行为和其他数字的行为一致,j正常地进行递增操作,无需退出循环
else {
j++;
}
}
}
// s[j]是运算符号
else if (s[j] == '+' || s[j] == '-' || s[j] == '*') {
// 如果s[j+1]是一个数字,则表达式合法,j可以进一步递增
if (isdigit(s[j+1])) {
j++;
}
// 否则,表达式只到当前s[j]结束,当前的合法表达式为s[i:j],退出循环
else {
break;
}
}
// s[j]是其他无关字符
// 表达式只到当前s[j]结束,当前的合法表达式为s[i:j],退出循环
else {
break;
}
}
// 如果上述关于while循环结束后,仍存在i == j
// 只可能是一种情况:
// 当前s[i] == s[j]为单个的"+"或"-"
// 且s[i+1] == s[j+1]的后面不是一个数字
// 此时表达式不存在,为空串,j递增
if (i == j) {
j++;
}
// 如果当前表达式存在,且长度大于之前储存的表达式的最大长度
// 则储存表达式的起始索引start_idx为i,终止索引end_idx为j
else if (j - i > end_idx - start_idx) {
start_idx = i;
end_idx = j;
}
// 将i设置为j,为下一个开始考虑的位置
i = j;
}
// 如果s[i]既不是数字,也不是"+"或"-"
// 则s[i]不能作为一个表达式的起始位置
// i直接递增
else {
i++;
}
}
// 如果最终sub_s不是一个空串,则可以进行表达式求值运算
if (start_idx < end_idx){
// 根据索引start_idx和end_idx来获得最终的最长合法表达式sub_s
string sub_s = s.substr(start_idx, end_idx - start_idx);
cout << cal(sub_s) << endl;
}
else {
cout << 0 << endl;
}
return 0;
}
时空复杂度
时间复杂度:O(N)
。
空间复杂度:O(N)
。
华为OD算法/大厂面试高频题算法练习冲刺训练
-
华为OD算法/大厂面试高频题算法冲刺训练目前开始常态化报名!目前已服务300+同学成功上岸!
-
课程讲师为全网50w+粉丝编程博主@吴师兄学算法 以及小红书头部编程博主@闭着眼睛学数理化
-
每期人数维持在20人内,保证能够最大限度地满足到每一个同学的需求,达到和1v1同样的学习效果!
-
60+天陪伴式学习,40+直播课时,300+动画图解视频,300+LeetCode经典题,200+华为OD真题/大厂真题,还有简历修改、模拟面试、专属HR对接将为你解锁
-
可上全网独家的欧弟OJ系统练习华子OD、大厂真题
-
可查看链接 大厂真题汇总 & OD真题汇总(持续更新)
-
绿色聊天软件戳
od1336
了解更多