栈解题思路

栈:栈(stack)又名堆栈,它是限定在表的一端进行插入和删除操作的线性表(后进先出)。这一端被称为栈顶,相对地,把另一端称为栈底。不含元素的空表称为空栈。

  • 英文stack
  • 先入后出的有序链表
  • 限制线性表中元素的插入删除只能在线性表的一端进行
  • 栈顶top、栈底bottom

在这里插入图片描述

栈的运用

  • 子程序的调用
  • 处理递归调用
  • 表达式的转换与求值
  • 二叉树的遍历
  • 图形的深度优先(depth-first)搜索法

栈的相关题目

20. 有效的括号

题目:给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串,判断字符串是否有效。有效字符串需满足:
左括号必须用相同类型的右括号闭合。左括号必须以正确的顺序闭合。注意空字符串可被认为是有效字符串。

  • 分析:由于括号和计算器的计算顺序都可以用先进后出来处理,所以利用栈的思想入栈左边的括号,遇到右括号就观察栈顶是否能够组成一对有效括号,如果可以则弹栈。最后如果栈内为空则有效。
  • 解题模板:
class Solution:
    def isValid(self, s: str) -> bool:
        dic = {'{': '}',  '[': ']', '(': ')', '?': '?'}
        stack = ['?']#加一个问号, 避免空栈的判断
        for c in s:
            if c in dic: stack.append(c)
            elif dic[stack.pop()] != c: return False 
        return len(stack) == 1

42. 接雨水

题目:给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。在这里插入图片描述上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。 感谢 Marcos 贡献此图。

  • 分析:每一个格子的积水量与其左右两边比它高的柱子高度中较小的那个高度有关。可以使用中心扩散的方法找左右两边的柱子高度,也可以使用栈的方法。维护一个递减栈,因为要形成一个凹坑就是要形成一个递减的栈,待加入的元素大于栈顶元数,那么这就是一个凹坑。
  • 计算方法,当待加入元素大于栈顶元数的时候,计算栈顶左右两百年高度较小的高度减去其本身的高度就得到了此柱所能等到的雨水高度,宽度就是左右两个柱子的索引减一,两者相乘就是其面积。
  • 解题模板:
class Solution:
    def trap(self, height: List[int]) -> int:
        length = len(height)
        if length < 3: return 0
        res, idx = 0, 0
        stack = []
        while idx < length:
            while len(stack) > 0 and height[idx] > height[stack[-1]]:
                top = stack.pop()  # index of the last element in the stack
                if len(stack) == 0:
                    break
                h = min(height[stack[-1]], height[idx]) - height[top]
                dist = idx - stack[-1] - 1
                res += (dist * h)
            stack.append(idx)
            idx += 1
        return res

84. 柱状图中最大的矩形

题目:给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
求在该柱状图中,能够勾勒出来的矩形的最大面积。

  • 分析:暴力双指针:首先选定一个柱子,左右分别寻找比这个主子高度低的索引坐标,大于等于本柱子高度则跳过。找到左右都比其大于等于的最远坐标left, right。使用 right - left +1得到底边的长度,再乘以本柱子的高度,得到单个柱子的最大面积。
  • 优化:我们可以存储一个递增的高度,如果之后要加进来的高度不是递增的,就表示找到了一个有边界。与此同时,我们由于栈中的高度是递增的,所以栈中的每个高度的左边界就是其左边的值的下标,这么一来,左右边界都确定了,高度也确定,那么就可以计算出每一个的面积,迭代算出最大值。
  • 注意:
    • 弹栈的时候,如果栈为空,就没有上一个记录的左边界值,所以在原始列表开头添加一个“哨兵” [0],防止原始列表内的值在栈内全部弹出后,找不到左边界。
    • 加入列表中的值全为递增,那么我们将所有值都入栈后找不到比栈中最上面小的值,于是就出不了栈。所以在原始列表的尾端加上一个“ 哨兵”,此处为[ 0 ]。这样列表内的值都可以出栈了。
  • 解题模板:
class Solution:
    def largestRectangleArea(self, heights: List[int]) -> int:
      stack = []
      heights = [0] + heights + [0]#建立一个含有哨兵的列表,首尾哨兵,假如是递增的列表数据,也可以弹栈
      res = 0
      for i in range(len(heights)):
      #栈不为空,即有左边界,并且带入栈的高度比栈顶高度小,所以不能入栈,需要出栈计算底边长度和面积
        while stack and heights[stack[-1]] > heights[i]:
          temp = stack.pop()#弹栈,这里面记录的是高度的下标
          #底边长度等于 i(右边界) - stack[-1](左边界) - 1
          res = max(res, (i - stack[-1] -1) * heights[temp])#计算最大面积
        stack.append(i)#如果栈为空则将此下标入栈
      return res

85. 最大矩形

题目:给定一个仅包含 0 和 1 的二维二进制矩阵,找出只包含 1 的最大矩形,并返回其面积。
示例:输入:
[
[“1”,“0”,“1”,“0”,“0”],
[“1”,“0”,“1”,“1”,“1”],
[“1”,“1”,“1”,“1”,“1”],
[“1”,“0”,“0”,“1”,“0”]
]
输出: 6

  • 分析:可以使用84题的方式求一个柱状图的最大矩形面积,本题的任务就是将每一行分割,求当前行以上的是否构成一个柱状图。对每一行以上进行最大矩形面积的计算。
  • 解题模板:
class Solution:
    def maximalRectangle(self, matrix: List[List[str]]) -> int:
        if not matrix: return 0
        m = len(matrix)
        n = len(matrix[0])
        out = 0
        #构建一个矩阵,每行的左右两端增加一个0
        column = [[0 for i in range(n+2)] for i in range(m)]

        for i in range(0, m):#将原始的字符矩阵换成数字矩阵
            for j in range(1, n+1):
                column[i][j] = int(matrix[i][j-1])
        #对数字矩阵的每行进行判断,如果每行有值(不为0),则累加该行上一行的对应元素值
        #这样做的目的是,构建m(行数)个柱形图。类似于leetcode 84题,使用84的求柱状图的最大面积
        #将一个矩阵,分解为行数个柱状图,进行求解。
        for i in range(1, m):
            for j in range(1,n+1):
                if column[i][j] == 1:
                    column[i][j] += column[i-1][j]

        #使用的是84题,单调递增栈的做法,计算柱状图的最大面积
        def max_area(s: List[int])->int:
            res = 0
            stack = []
            for i in range(n+2):
                while stack and s[stack[-1]] > s[i]:
                    height_index = stack.pop()
                    res = max(res, (i - stack[-1] - 1) * s[height_index])
                stack.append(i)
            return res
        #循环重复m次,计算出每一行的最大柱状面积,再找一个最大值输出
        for i in range(m):
            out = max(out, max_area(column[i]))
        return out

150. 逆波兰表达式求值

题目:根据 逆波兰表示法,求表达式的值。有效的运算符包括 +, -, *, / 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。说明:整数除法只保留整数部分。给定逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。

  • 分析:适合用栈操作运算:遇到数字则入栈;遇到算符则取出栈顶两个数字进行计算,并将结果压入栈中。
  • 解题模板:
class Solution:
    def evalRPN(self, tokens: List[str]) -> int:
        stack = []
        for i in range(len(tokens)):
            if tokens[i] not in ['+', '-', '*', '/']:#是数字就入栈
                stack.append(int(tokens[i]))
            else:#是运算符就弹栈,根据运算符及进行计算
                num2 = int(stack.pop())#弹出两个数字
                num1 = int(stack.pop())
                if tokens[i] == '*':
                    stack.append(num1*num2)
                elif tokens[i] == '/':
                    stack.append(int(num1 / num2))
                elif tokens[i] == '+':
                    stack.append(num1+num2)
                elif tokens[i] == '-':
                    stack.append(num1-num2)
        return stack[-1]  #最后只有一个数字

316. 去除重复字母

题目:给你一个仅包含小写字母的字符串,请你去除字符串中重复的字母,使得每个字母只出现一次。需保证返回结果的字典序最小(要求不能打乱其他字符的相对位置)。

  • 分析:用栈来存储最终返回的字符串,并维持字符串的最小字典序。每遇到一个字符,如果这个字符不存在于栈中,就需要将该字符压入栈中。但在压入之前,需要先将之后还会出现,并且字典序比当前字符大的栈顶字符移除,然后再将当前字符压入。
  • 解题模板:
class Solution:
    def removeDuplicateLetters(self, s: str) -> str:
        if not s: return ""
        n = len(s)
        stack = [s[0]]
        for i in range(1,n):
            if s[i] not in stack:#首先判断是否在栈中,如果已经包含了,那么就是无需再次排序
                while stack and s[i] < stack[-1] and stack[-1] in s[i+1:]:#栈顶元素之后还有,并且小于待加入的元素,那么可以弹栈。
                    stack.pop()
                stack.append(s[i])
        return ''.join(stack)

394. 字符串解码

题目:给定一个经过编码的字符串,返回它解码后的字符串。编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k ,例如不会出现像 3a 或 2[4] 的输入。

  • 分析:由于是字符串顺序存储,所以这里考虑使用栈来存储,

    • 遇到数字就进行累计,计算倍数。
    • 遇到左括号, 表示数字倍数计算完毕, 倍数入栈,缓存的字符串也入栈。
    • 遇到字符就临时存储起来等着右括号来结束存储
    • 遇到右括号,表示字符缓存完毕, 将当前的缓存字符串乘以弹栈的倍数,再和弹栈的字符串相加,进入缓存
    1. 字符串的重复个数可能是大于十的,所以需要累加
    2. 字符可能是小写,也可能是大写,小写和大写中间的字符还包括了 " [ ", " ] "。
    3. 开始的时候每次我只存上一步的 乘数和重复后的字符串。导致多重括号失效
    4. 这里的栈是用来存储两个东西,上一段的重复串,和当前【】内的重复次数
    5. 当没有括号的时候,就是表示这个字符串的个数是1,由于这里是直接存在stack中,相当于一个前串。否则按照老套路非要找一个【】来计算重复次数,就很难。
    6. 总之,这里stack.append([mul, res])是亮点,同时入栈的之后的倍数和当前的字串。
  • 解题模板:

class Solution:
    def decodeString(self, s: str) -> str:
        stack = []
        mul = 0
        res = ""
        # out = ""
        for item in s:
            if item == '[':#已经算出之前的重复串,得到了当前的乘数值,保存在栈中
                stack.append([mul, res])
                mul, res = 0, ""#保存之后清零
            elif 'A' <= item <= 'Z' or 'a' <= item <= 'z':#这里有可能是大写,有可能是小写
                res += item
            elif item == ']':#尾括号就应该计算放前的重复串,计算后和之前的重复串相加
                cur_mul, pre_res = stack.pop()#弹出上一个重复串和本次的乘数
                res = pre_res + cur_mul * res
            else:
                mul = mul*10+int(item)#乘数有可能是多位数
        return res

402. 移掉K位数字

题目:给定一个以字符串表示的非负整数 num,移除这个数中的 k 位数字,使得剩下的数字最小。输入: num = “1432219”, k = 3。输出: “1219”。解释: 移除掉三个数字 4, 3, 和 2 形成一个新的最小的数字 1219。

  • 分析:
    1. 首先计算出最终会有多少个保留的数
    2. 如果待加入的数小于栈顶的数,就弹栈,在弹栈的时候,待删除的数的数量减一
    3. 如果是递增的,或者已经没有要删除的数了,那么直接加入栈中。
    4. 最后输出栈中的元数,并且将左边的0去掉,如果去掉左0之后为空则返回 ‘0’
  • 注意:可能没有那么多需要删除的数字,例如数组是一个递增的数组,那么我们就取前面的位次
  • 解题模板:
class Solution:
    def removeKdigits(self, num: str, k: int) -> str:
        if not num: return '0'
        n = len(num)
        if n == k: return '0'
        remain = n - k#计算最终列表中会有多少个元素,取前面的多少位
        stack = [num[0]]

        for i in range(1, n):
            while stack and num[i] < stack[-1] and k:#栈顶元数大于待加入的元数,并且还能弹出,则弹栈
                stack.pop()
                k -= 1
            stack.append(num[i])#直到不能弹栈就入栈,这会导致之后元数都会入栈
        print(stack)
        return ''.join(stack[:remain]).lstrip('0')  or '0'#如果输出为空,则输出0     

739. 每日温度

题目:请根据每日 气温 列表,重新生成一个列表。对应位置的输出为:要想观测到更高的气温,至少需要等待的天数。如果气温在这之后都不会升高,请在该位置用 0 来代替。例如,给定一个列表temperatures = [73, 74, 75, 71, 69, 72, 76, 73],你的输出应该是 [1, 1, 4, 2, 1, 1, 0, 0]。提示:气温 列表长度的范围是 [1, 30000]。每个气温的值的均为华氏度,都是在 [30, 100] 范围内的整数。

  • 分析:如果不使用栈的方法,那么使用两次for循环的时间复杂度就是O(n^2)。所以这里选择使用的是单调栈,这里具体使用的是一个单调递减栈。因为如果待加入的温度比栈顶当前的温度要高,那么就会产生一个弹栈的操作,因为此时栈顶的元素索引对应的温度值小于待加入的温度值,那么我们就会记录下此时待加入的温度值索引差到栈顶对应的索引值内。
  • 解题模板:
class Solution:
    def dailyTemperatures(self, T: List[int]) -> List[int]:  
        stack = []
        out = [0]*len(T)
        for i, t in enumerate(T):#使用枚举类型,方便计算索引和对比温度值
            while stack and T[stack[-1]] < t:#当前栈不为空,并且栈顶的温度小于之后待加入的温度
                out[stack.pop()] = i - stack[-1]#弹栈,填入两者之间的索引差
            stack.append(i)
        return out

946. 验证栈序列

题目:给定 pushed 和 popped 两个序列,每个序列中的 值都不重复,只有当它们可能是在最初空栈上进行的推入 push 和弹出 pop 操作序列的结果时,返回 true;否则,返回 false

  • 分析:每次循环我们都push数字入栈。对比当前待出栈的元数是否在栈顶,如果不在,就继续循环入栈。如果当前栈顶的元素和待出栈的元素相同,那么进行pop出栈,弹栈之后popped序列的索引后移,继续看此时待出栈的元素和栈顶元素是否相同。
  • 解题模板:
class Solution:
    def validateStackSequences(self, pushed: List[int], popped: List[int]) -> bool:
        stack, i = [], 0
        for num in pushed:
            stack.append(num)#每次加入元数入栈
            while stack and stack[-1] == popped[i]:#循环判断出栈
                stack.pop()
                i += 1#出栈后弹栈的索引加一
        return not stack

5470. 平衡括号字符串的最少插入次数

题目:给你一个括号字符串 s ,它只包含字符 ‘(’ 和 ‘)’ 。一个括号字符串被称为平衡的当它满足:任何左括号 ‘(’ 必须对应两个连续的右括号 ‘))’ 。左括号 ‘(’ 必须在对应的连续两个右括号 ‘))’ 之前。比方说 “())”, “())(())))” 和 “(())())))” 都是平衡的, “)()”, “()))” 和 “(()))” 都是不平衡的。你可以在任意位置插入字符 ‘(’ 和 ‘)’ 使字符串平衡。请你返回让 s 平衡的最少插入次数。

  • 分析:先把s入栈,每次弹出一个,判断是左括号还是右括号。如果是右括号,继续弹栈。如果是左括号,判断res内的右括号个数是否为奇数,如果是奇数,那么加一个,变成偶数,弹出两个。最后res里面全是右括号,或者为空。还有右括号就对右括号进行。
  • 解题模板:
class Solution:
    def minInsertions(self, s: str) -> int:
        if not s: return 0
        n = len(s)
        stack = []
        for i in range(n):#入栈
            stack.append(s[i])
        res = []
        cnt = 0
        while stack:#弹栈
            temp = stack.pop()
            if temp == ')':#右括号就入res栈
                res.append(')')
            else:#左括号先判断res内的奇偶
                if len(res) & 1:
                    res.append(')')
                    cnt += 1
                if res:
                    res.pop()
                    res.pop()
                else:
                    cnt += 2
        if res:#如果弹栈完成,就剩余的右括号配对
            n = len(res)
            if n & 1:
                cnt += n//2 + 2
            else:
                cnt += n//2

        return cnt
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值