栈:从基础概念到实战解题(详细)

一、栈的核心概念:藏在生活里的逻辑

如果你经常做饭,肯定有过这样的经历:洗完的盘子要一个叠一个放,取的时候也得从最上面那个开始拿。这种 "后进先出" 的规则,其实就是栈的核心逻辑。在编程里,栈(Stack)是一种特殊的线性结构,它只允许在一端(栈顶)进行操作,另一端(栈底)则固定不动。

比如你用浏览器上网时,每打开一个新页面,就像往栈里放了一个盘子;点 "后退" 按钮,就相当于从栈顶拿走最上面的盘子,回到上一个页面。这种严格的操作顺序,让栈在处理有明确先后依赖的问题时特别好用。

栈的基本操作很简单,就像叠盘子的动作:

  • 入栈(Push) :把新元素放到栈顶,比如把刚洗好的盘子放到最上面
  • 出栈(Pop) :把栈顶元素拿走,比如从最上面取一个盘子用
  • 查看栈顶(Peek) :看看最上面是什么,但不拿走,比如确认下最上面盘子的花色
  • 判空(isEmpty) :检查栈里有没有元素,就像看看盘子架上是不是空的

二、栈的两种实现方式:各有各的用场

实现栈有两种常用方式,就像存放盘子可以用固定大小的盘子架,也可以用能无限延伸的架子:

数组实现的栈

用数组实现栈时,我们可以把数组的末尾当作栈顶。比如定义一个数组stack和一个记录栈顶位置的变量top,初始时top = -1表示栈为空。

  • 入栈时,top加 1,然后把元素放到stack[top]的位置
  • 出栈时,先取出stack[top]的元素,再把top减 1

这种方式的优点是操作速度快,因为数组的内存是连续的;但缺点也明显,就像固定大小的盘子架,装满了就放不下了,需要提前知道大概有多少元素。

链表实现的栈

用链表实现时,我们把链表的头部当作栈顶,每个节点包含数据和指向下一个节点的指针。

  • 入栈时,新建一个节点,让它的指针指向当前栈顶,再把栈顶更新为这个新节点
  • 出栈时,取出栈顶节点的数据,再把栈顶更新为它的下一个节点

这种方式就像能无限延伸的架子,不用担心装不下,但每次操作都要处理指针,稍微麻烦一点。

实际开发中,如果知道数据量大概范围,优先用数组实现;如果数据量不确定,用链表更合适。

三、面试常考的栈算法题:结合力扣题目代码分析

1. 有效的括号(力扣第 20 题)

题目描述:给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。有效字符串需满足:左括号必须用相同类型的右括号闭合;左括号必须以正确的顺序闭合。

解题思路

  • 遍历字符串,遇到左括号((、{、[)就入栈
  • 遇到右括号时,检查栈顶元素是否是对应的左括号:
    • 如果是,就把栈顶元素出栈
    • 如果不是,或者栈已经为空,说明匹配失败
  • 遍历结束后,如果栈为空,说明所有括号都正确匹配

代码实现

arduino 复制代码
def isValid(s: str) -> bool:
    stack = []
    mapping = {')': '(', '}': '{', ']': '['}
    for char in s:
        if char in mapping:
            # 遇到右括号,若栈为空则用'#'作为占位符,避免pop报错
            top_element = stack.pop() if stack else '#'
            if mapping[char] != top_element:
                return False
        else:
            # 遇到左括号入栈
            stack.append(char)
    # 栈为空说明所有左括号都有匹配的右括号
    return not stack

实例分析

以s = "(()]"为例,遍历过程如下:

  • 第一个字符 '(', 入栈,栈为 ['(']
  • 第二个字符 '(', 入栈,栈为 ['(', '(']
  • 第三个字符 ')', 此时 mapping [')'] 为 '(', 栈顶元素为 '(', 匹配成功,出栈,栈为 ['(']
  • 第四个字符 ']',mapping [']'] 为 '[', 栈顶元素为 '(', 不匹配,返回 False

2. 逆波兰表达式求值(力扣第 150 题)

题目描述:根据 逆波兰表示法,求表达式的值。有效的算符包括 +、-、*、/ 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。注意两个整数相除时,只保留整数部分。

解题思路

  • 遍历表达式,遇到数字就入栈
  • 遇到运算符,就从栈里弹出两个数字,用这个运算符计算后,把结果再入栈
  • 最后栈里剩下的那个数字就是结果

代码实现

ini 复制代码
def evalRPN(tokens: list[str]) -> int:
    stack = []
    for token in tokens:
        if token in '+-*/':
            # 弹出两个操作数,注意顺序,后弹出的是第一个操作数
            b = stack.pop()
            a = stack.pop()
            if token == '+':
                stack.append(a + b)
            elif token == '-':
                stack.append(a - b)
            elif token == '*':
                stack.append(a * b)
            else:
                # 处理除法,注意符号和整数部分
                stack.append(int(a / b))
        else:
            # 数字入栈,转换为整数
            stack.append(int(token))
    return stack[0]

实例分析

以tokens = ["4","13","5","/","+"]为例:

  • "4" 入栈,栈为 [4]
  • "13" 入栈,栈为 [4,13]
  • "5" 入栈,栈为 [4,13,5]
  • 遇到 "/", 弹出 5 和 13,计算 13/5=2(整数部分),入栈,栈为 [4,2]
  • 遇到 "+", 弹出 2 和 4,计算 4+2=6,入栈,栈为 [6]
  • 遍历结束,返回 6

3. 栈的压入、弹出序列(力扣第 31 题)

题目描述:输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。

解题思路

  • 用一个辅助栈模拟压入弹出过程
  • 按压入顺序把元素压入辅助栈,每次压入后,检查栈顶是否和弹出序列的当前元素相等
  • 如果相等,就弹出栈顶元素,并移动弹出序列的指针
  • 最后如果辅助栈为空,说明弹出序列有效

代码实现

arduino 复制代码
def validateStackSequences(pushed: list[int], popped: list[int]) -> bool:
    stack = []
    pop_index = 0  # 记录弹出序列的当前位置
    for num in pushed:
        stack.append(num)
        # 当栈顶元素和弹出序列当前元素相同时,弹出并移动指针
        while stack and stack[-1] == popped[pop_index]:
            stack.pop()
            pop_index += 1
    # 辅助栈为空说明所有元素都按弹出序列弹出
    return not stack

实例分析

以pushed = [1,2,3,4,5], popped = [4,5,3,2,1]为例:

  • 1 入栈,栈为 [1],栈顶 1 != popped [0]=4,继续
  • 2 入栈,栈为 [1,2],栈顶 2 !=4,继续
  • 3 入栈,栈为 [1,2,3],栈顶 3 !=4,继续
  • 4 入栈,栈为 [1,2,3,4],栈顶 4 ==4,弹出,pop_index=1,栈为 [1,2,3]
  • 此时栈顶 3 != popped [1]=5,继续压入 5,栈为 [1,2,3,5]
  • 栈顶 5 ==5,弹出,pop_index=2,栈为 [1,2,3]
  • 栈顶 3 ==3,弹出,pop_index=3,栈为 [1,2]
  • 栈顶 2 ==2,弹出,pop_index=4,栈为 [1]
  • 栈顶 1 ==1,弹出,pop_index=5,栈为空
  • 最终栈为空,返回 True

四、栈的常见错误用法:这些坑要避开

  • 把栈和堆搞混:栈是一种数据结构,而堆是内存中的一块区域,两者没什么关系
  • 递归时不控制深度:递归调用会用到系统栈,递归太深会导致栈溢出
  • 该用栈的时候不用:遇到需要 "后进先出" 处理的问题,比如括号匹配、历史记录等,用栈会比其他结构简单得多

煮播看了这篇文章以后,端盘子都更有逻辑了呢哈哈哈哈哈哈哈哈哈

相关推荐
mashanshui1 小时前
Https之(二)TLS的DH密钥协商算法
算法·https·tls·dh·ecdhe
wearegogog1234 小时前
MATLAB的脉搏信号分析预处理
算法·matlab
fs哆哆4 小时前
在VB.net中一维数组,与VBA有什么区别
java·开发语言·数据结构·算法·.net
wjt1020204 小时前
机器学习--续
算法·机器学习
牵星术小白5 小时前
【GNSS基带算法】Chapter.2 相干积分与非相干积分
算法
哇哈哈QIQ6 小时前
2025.7.19卡码刷题-回溯算法-组合
算法
gihigo19988 小时前
matlab多目标优化差分进化算法
数据结构·算法
weixin_582470179 小时前
GS-IR:3D 高斯喷溅用于逆向渲染
人工智能·算法
Lin9成9 小时前
机器学习集成算法与K-means聚类
算法
JNU freshman10 小时前
算法 之 拓 扑 排 序
数据结构·算法