栈刷题记录总结 | 豆包MarsCode AI刷题

25 阅读7分钟

前言

代码写得比较丑,求大佬轻喷

括号补全

问题

小R有一个括号字符串 s,他想知道这个字符串是否是有效的。一个括号字符串如果满足以下条件之一,则是有效的:

  • 它是一个空字符串,或无括号的字符串
  • 它可以写成 (A) 的形式,其中 A 是有效字符串。
  • 在每次操作中,小R可以在字符串的任意位置插入一个括号。你需要帮小R计算出,最少需要插入多少个括号才能使括号字符串 s 有效。

例如:当 s = "())" 时,小R需要插入一个左括号使字符串有效,结果为 1。

分析

这个问题本质就是求没有匹配的括号的数目,比如对于字符串 "))((",我们需要现在字符串的左端插入两个(,再在最右端插入两个),可以不严谨地证明我们需要添加的括号数目就是未平衡的括号的数目。

思路

思路就是使用栈来存储左括号。碰到左括号,就将其压入栈中。如果碰到右括号,就检查栈顶的左括号是否匹配(栈不为空且栈顶是左括号),如果匹配,就将栈顶元素弹出栈,否则将右括号压栈(栈为空或栈顶是右括号)。剩下栈中元素数目就是待匹配括号数目,也是我们需要添加的字符数。

实现

我们使用一个数组模拟栈,我们使用python列表自带的popa[:-1],将列表最后一个元素去除,模拟弹栈过程,使用append将元素加到列表末尾,模拟压栈过程,列表最后一个元素就表示栈顶元素。 我们可以写出如下代码

def solution(s: str) -> int:
    if s == '':
        return 0
    stack = []
    for e in s:
        if len(stack) > 0 and e == ')' and stack[-1] == '(':
            stack = stack[:-1]
            continue
        stack.append(e)
    return len(stack)

吐槽

你需要考虑的是整个字符串的括号平衡。栈中剩余的元素数量并不总是等于需要插入的括号数量。例如,对于字符串 "))((",栈中剩余的元素数量是 4,但实际上只需要插入 2 个括号。 以上的话出自Marcode AI。但我们确实找不到只插入2个括号就让括号平衡的方法。Marcode AI还是需要改进啊,希望可以越来越好。

相邻重复字母删除

问题

小M拿到了一个由小写字母组成的字符串 s。她发现可以进行一种操作:选择两个相邻且相同的字母并删除它们。她可以在 s 上反复进行此操作,直到无法再删除任何字母。 请返回最终处理完所有重复项删除操作后的字符串。可以保证返回的答案是唯一的。

分析

同样可以使用栈完成,思路类似于上一题。碰到一个字母,我们将其与栈顶元素进行比较,如果相等,就将栈顶元素弹出栈,否则将读取到的字符压栈。 当然也可以使用双指针实现,一个指针指向前一个元素,另一个指针指向当前读到的元素。当两个指针读到的内容相同时,前指针前移一位(如果前指针对应的元素的索引不为0)或者置空(如果前指针对应的元素的索引为0)。否则两个指针同时后移。不过实现逻辑比较负责,且需要的代码较多,不如栈方法简洁。

实现

同样使用列表模拟栈

def solution(s: str) -> str:
    stack = []
    for e in s:
        if len(stack) > 0 and stack[-1] == e:
            stack = stack[:-1]
            continue
        stack.append(e)
    return ''.join(stack)

四则运算

warning:代码写得很丑

我们使用两个栈,一个栈用于保存操作数,一个用于保存运算符号。(我们默认表达式合法,没有实现各种异常情况的处理)

  • 读取数字:我们使用一个字符串存储读到的数字串,当我们读到第一个数字时,开始向该字符串存储,直到读取到第一个符号为止。之后我们使用python自带的int函数,将字符串转为数字。也可以在读到每个数字字符时就计算,并将原来的数字乘以10。,之后,我们将操作数压如操作数栈。
  • 读取到(,因为(满足[\+-\*\/]?,即(只有可能为加减乘除号或为空,不可能跟着数字,所以可以直接压入符号栈。跳过后续所有操作。
  • 读取到),括号内表达式计算优先级大于加减,因此我们先计算括号内的表达式。根据我们对乘除运算和括号的处理,括号内此时剩余的符号全部为加号或减号,因此不存在顺序问题,可以直接顺序计算,直到读取到(。括号内运算的结果压入栈。接着进行之后的操作。
  • 栈顶为*/:因为乘除的运算优先级高,我们碰到乘除号就直接计算,计算方式是提取操作数栈的栈顶两个元素,并将符号弹栈,进行相应计算,并将结果压栈。这个可以保证乘除操作先进行,后续的运算操作只剩加减号。
  • 读取到的符号若为减号,就将减号存入字符串,这样解析数字时可以将数字转为负数,等价于一个操作数与一个负数进行加法运算。
  • 将当前读取的符号存入栈中。 由此实现整体逻辑
digital = '0123456789'
def calc(v1, v2, exp):
    if exp == '+':
        return v1 + v2
    if exp == '-':
        return v1 - v2
    if exp == '*':
        return v1 * v2
    if exp == '/':
        return v1 // v2
    return -1

def pop_and_calc(stack_int, stack_exp):
    val1 = stack_int[-1]
    val2 = stack_int[-2] 
    exp = stack_exp[-1]
    return calc(val2, val1, exp)

def solution(expression):
    # Please write your code here
    stack_int = []
    stack_exp = []
    tmp_num = ''
    for e in expression:
        if e in digital:
            tmp_num += e
        else:
            if e == '(':
                stack_exp.append(e)
                continue
            if tmp_num != '':
                stack_int.append(int(tmp_num, 10))
                tmp_num = ''
            if e == ')':
                while len(stack_exp) > 0 and stack_exp[-1] != '(':
                    res = pop_and_calc(stack_int, stack_exp)
                    stack_int = stack_int[:-1]
                    stack_exp = stack_exp[:-1]
                    stack_int[-1] = res
                if stack_exp[-1] == '(':
                    stack_exp = stack_exp[:-1]
            if len(stack_exp) > 0 and stack_exp[-1] in '*/':
                res = pop_and_calc(stack_int, stack_exp) 
                stack_exp = stack_exp[:-1]
                stack_int = stack_int[:-1]
                stack_int[-1] = res
            if e == '-':
                tmp_num += '-'
                stack_exp.append('+')
                continue
            if e != ')':
                stack_exp.append(e)
    if tmp_num != '':
        stack_int.append(int(tmp_num, 10))
    while len(stack_exp) > 0:
        res = pop_and_calc(stack_int, stack_exp) 
        stack_int = stack_int[:-1]
        stack_exp = stack_exp[:-1]
        stack_int[-1] = res

    return stack_int[-1]

栈知识总结

以下内容总结自Marscode AI,总结得很全面,我也没什么需要补充的。

栈(Stack)是一种常见的数据结构,它遵循后进先出(LIFO, Last In First Out)的原则。栈可以想象成一堆盘子,你只能从最上面取盘子,也只能在最上面放盘子。

栈的基本操作

  1. Push(入栈):将一个元素添加到栈的顶部。
  2. Pop(出栈):从栈的顶部移除一个元素,并返回该元素。
  3. Peek(查看栈顶元素):返回栈顶的元素,但不移除它。
  4. IsEmpty(判断栈是否为空):检查栈是否为空。
  5. Size(获取栈的大小):返回栈中元素的数量。

栈的应用场景

栈在很多算法和数据结构中都有广泛的应用,例如:

  1. 括号匹配:在处理括号匹配问题时,栈是一个非常有效的工具。例如,在检查一个字符串中的括号是否匹配时,可以使用栈来存储左括号,并在遇到右括号时检查栈顶的左括号是否匹配。
  2. 函数调用栈:在编程语言中,函数调用栈用于管理函数的调用和返回。每次调用一个函数时,当前的执行状态(如局部变量、返回地址等)会被压入栈中,函数返回时再从栈中弹出。
  3. 表达式求值:在计算表达式时,栈可以用于处理运算符的优先级和括号的嵌套。
  4. 浏览器历史记录:浏览器的前进和后退功能通常使用栈来实现。每次访问一个新页面时,当前页面会被压入栈中,后退时则从栈中弹出。