【Py/Java/C++三种语言OD独家2024D卷真题】20天拿下华为OD笔试之【双指针】2024D-提取字符串中最长数学表达式【欧弟算法】全网注释最详细分类最全的华为OD真题题解

22 篇文章 0 订阅
8 篇文章 0 订阅

有LeetCode算法/华为OD考试扣扣交流群可加 948025485
可上全网独家的 欧弟OJ系统 练习华子OD、大厂真题
绿色聊天软件戳 od1441了解算法冲刺训练(备注【CSDN】否则不通过)

从2024年4月15号开始,OD机考全部配置为2024D卷
注意两个关键点:

  1. 会遇到C卷复用题。虽然可能存在幸存者偏差,但肯定还会有一大部分的旧题。
  2. 现在又支持做完题目之后倒回去改了。就是可以先做200的再做100的,然后可以反复提交。
    在这里插入图片描述

题目描述与示例

题目描述

提取字符串中的最长合法简单数学表达式,字符串长度最长的,并计算表达式的值。如果没有,则返回0

简单数学表达式只能包含以下内容:0-9数字,符号 +-*

说明:

  1. 所有数字,计算结果都不超过long
  2. 如果有多个长度一样的,请返回第一个表达式的结果
  3. 数学表达式,必须是最长的,合法的
  4. 操作符不能连续出现,如 +--+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-"不是合法表达式

因此,关于合法表达式的提取,需要进行非常多的分类讨论。

合法表达式双指针框架

我们可以使用两个指针ij来进行合法表达式的提取。

其中,i表示合法表达式的起始位置,j表示合法表达式的终止位置。譬如

abcd1-2abcd3+4*5
    ↑  ↑
    i  j

对于任何一个合法表达式(无论是否最长),我们就能够使用s[i:j]来表示这个表达式。

显然区间[i, j)是一个左闭右开区间,这样做的好处是,当考虑完一个合法表达式之后,我们都可以进一步地以j的位置作为新的i,来考虑后续的新的表达式。

另外,由于ij的修改条件相对复杂,很难使用for循环来完成,因此很容易想到关于ij的前进都应该用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]就是一个无法继续延长的合法表达式,可以直接退出关于jwhile循环。譬如

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

将三种情况均讨论完毕后,关于jwhile循环内的内容也就完成了。我们更新代码框架如下

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

退出内层循环后的答案更新

再次重申,退出关于jwhile循环后,此时的表达式为s[i:j]j表示右开区间。

在关于jwhile循环中,单个"+""-"并不会被认为是一个合法表达式。

譬如"++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_idxend_idx来储存全局的最长合法表达式的起始位置和终止位置。

初始化start_idxend_idx相等(即它们做差为0),当发现j - i > start_idx - end_idx的时候,我们将start_idxend_idx分别修改为ji即可。

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,是因为题目要求当出现多个最长合法表达式时,选择第一个最长合法表达式

>可以保证,只有在找到更长的表达式的时候才进行更新。

如果题目要求当出现多个最长合法表达式时,选择最后一个最长合法表达式的话,那么应该使用>=

这样就可以在找到等长的最长表达式时,也进行更新了,最终结果一定是最后一个最长合法表达式。

在退出关于iwhile循环之后,全局的start_idxend_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)

避免越界的技巧

在上述关于jwhile循环中,我们的代码其实并没有讨论关于j的越界情况。

实际上在循环中是有可能出现越界的,因为我们在循环中多次取了s[j-1]s[j+1]来进行判断。

但因为分类讨论的情况很多,比较复杂,如果反复地进行越界判断会使得代码可读性变得非常差,也不利于我们进行调试和修改。

在最终的代码中,我们用上了这样一个技巧:在循环开始之前,往原字符串s的前后均填充了一个不会影响最终结果的字符

s = "a" + s + "a"

无论原来的s的最开头和最末尾是什么字符,新填充的两个无关字符必然不会被包含在表达式中,那么在关于jwhile循环中,我们在取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了解更多

  • 22
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值