1. 项目概述
最近在整理LeetCode热题100中的栈相关题目时,发现这组题目特别能体现栈结构的精髓。从基础的括号匹配到复杂的单调栈应用,这些题目涵盖了栈在算法中的典型使用场景。作为数据结构中最基础也最重要的线性结构之一,栈的"后进先出"特性在解决特定问题时往往能带来意想不到的简洁解法。
这五道题目(有效的括号、最小栈、字符串解码、每日温度、柱状图中最大的矩形)由浅入深,既包含了栈的基础应用,也展示了高级技巧。我在刷题过程中发现,很多同学对栈的理解停留在表面,遇到稍微复杂的问题就不知如何下手。本文将详细拆解每道题目的解题思路,分享我在解决这些问题时积累的经验和技巧。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 栈基础与题目解析
2.1 栈的核心特性与应用场景
栈(Stack)是一种操作受限的线性表,只允许在一端(栈顶)进行插入和删除操作,遵循后进先出(LIFO)原则。这种特性使其特别适合处理具有嵌套结构的问题,比如:
- 括号匹配:需要检查最近打开的括号是否先闭合
- 函数调用:系统使用调用栈管理函数调用关系
- 表达式求值:处理运算符优先级和括号嵌套
- 撤销操作:按相反顺序执行撤销
在算法题中,栈的应用可以大致分为三类:
- 直接利用LIFO特性(如有效的括号)
- 辅助栈记录额外信息(如最小栈)
- 单调栈解决特定问题(如每日温度)
2.2 题目难度与技巧分布
这五道题目按照难度和技巧可以分为三个层次:
| 题目 | 难度 | 核心技巧 | 时间复杂度 |
|---|---|---|---|
| 有效的括号 | 简单 | 基础栈应用 | O(n) |
| 最小栈 | 中等 | 辅助栈 | O(1)各操作 |
| 字符串解码 | 中等 | 双栈配合 | O(n) |
| 每日温度 | 中等 | 单调栈 | O(n) |
| 柱状图中最大的矩形 | 困难 | 单调栈+边界处理 | O(n) |
3. 题目详解与实现
3.1 有效的括号(LeetCode 20)
这是最经典的栈应用题目。给定一个只包含 '(', ')', '{', '}', '[' 和 ']' 的字符串,判断字符串是否有效。
解题思路:
- 遇到左括号入栈
- 遇到右括号时,检查栈顶是否匹配
- 最后检查栈是否为空
python复制def isValid(s: str) -> bool:
stack = []
mapping = {')': '(', '}': '{', ']': '['}
for char in s:
if char in mapping:
top = stack.pop() if stack else '#'
if mapping[char] != top:
return False
else:
stack.append(char)
return not stack
注意事项:
- 边界情况处理:空字符串、单字符、只有左括号
- Python中可以用字典简化匹配逻辑
- 实际面试时要注意代码的简洁性和可读性
3.2 最小栈(LeetCode 155)
设计一个支持 push、pop、top 操作,并能在常数时间内检索到最小元素的栈。
解题思路:
使用辅助栈同步记录当前最小值:
python复制class MinStack:
def __init__(self):
self.stack = []
self.min_stack = []
def push(self, val: int) -> None:
self.stack.append(val)
if not self.min_stack or val <= self.min_stack[-1]:
self.min_stack.append(val)
def pop(self) -> None:
if self.stack.pop() == self.min_stack[-1]:
self.min_stack.pop()
def top(self) -> int:
return self.stack[-1]
def getMin(self) -> int:
return self.min_stack[-1]
优化技巧:
- 辅助栈可以只存储值和出现次数,节省空间
- 比较时使用<=而非<,处理重复最小值情况
- 线程安全版本需要考虑锁机制
3.3 字符串解码(LeetCode 394)
给定一个编码字符串,返回它解码后的字符串。如 "3[a]2[bc]" 解码为 "aaabcbc"。
解题思路:
使用数字栈和字符串栈分别处理:
python复制def decodeString(s: str) -> str:
num_stack, str_stack = [], []
current_str, current_num = "", 0
for c in s:
if c.isdigit():
current_num = current_num * 10 + int(c)
elif c == '[':
num_stack.append(current_num)
str_stack.append(current_str)
current_num, current_str = 0, ""
elif c == ']':
num = num_stack.pop()
prev_str = str_stack.pop()
current_str = prev_str + num * current_str
else:
current_str += c
return current_str
易错点:
- 数字可能不止一位,需要累加计算
- 嵌套解码时注意字符串拼接顺序
- 内存优化:对于超长字符串可以考虑生成器实现
3.4 每日温度(LeetCode 739)
给定一个温度列表,返回一个列表表示需要等待多少天才能观察到更高的温度。
单调栈解法:
维护一个温度递减的栈,遇到更高温度时弹出计算:
python复制def dailyTemperatures(T: List[int]) -> List[int]:
stack = []
result = [0] * len(T)
for i, temp in enumerate(T):
while stack and T[stack[-1]] < temp:
prev = stack.pop()
result[prev] = i - prev
stack.append(i)
return result
单调栈要点:
- 栈中存储的是索引而非温度值
- 遇到更高温度时才触发计算
- 结果是当前索引与栈顶索引的差
- 时间复杂度O(n),每个元素最多入栈出栈一次
3.5 柱状图中最大的矩形(LeetCode 84)
给定n个非负整数表示的柱状图,找出其中能勾勒出的最大矩形的面积。
单调栈优化解法:
python复制def largestRectangleArea(heights: List[int]) -> int:
heights = [0] + heights + [0]
stack = []
max_area = 0
for i in range(len(heights)):
while stack and heights[stack[-1]] > heights[i]:
h = heights[stack.pop()]
w = i - stack[-1] - 1
max_area = max(max_area, h * w)
stack.append(i)
return max_area
关键点解析:
- 添加哨兵节点简化边界处理
- 维护一个高度递增的单调栈
- 当遇到较小高度时,计算之前较高柱子的最大面积
- 宽度计算是当前索引与栈顶前一个索引的差减一
4. 单调栈深度解析
4.1 单调栈的两种形式
单调栈分为单调递增栈和单调递减栈:
- 递增栈:栈底到栈顶元素递增,用于寻找下一个更小元素
- 递减栈:栈底到栈顶元素递减,用于寻找下一个更大元素
选择原则:
- 求第一个更大元素 → 递减栈
- 求第一个更小元素 → 递增栈
- 求左右边界 → 通常用递增栈
4.2 单调栈模板代码
python复制def monotonic_stack_template(nums):
stack = []
result = [0] * len(nums)
for i in range(len(nums)):
while stack and nums[stack[-1]] < nums[i]: # 根据需求调整比较符号
prev = stack.pop()
result[prev] = i - prev # 根据题目调整计算结果
stack.append(i)
return result
4.3 单调栈的典型应用场景
- 下一个更大/小元素(LeetCode 496, 503)
- 接雨水问题(LeetCode 42)
- 最大矩形问题(LeetCode 85)
- 股票跨度问题(LeetCode 901)
5. 刷题经验与技巧
5.1 栈问题解题步骤
- 识别问题是否具有嵌套或最近相关性
- 确定使用普通栈还是单调栈
- 设计栈中存储的信息(值、索引、还是复合信息)
- 明确入栈和出栈的触发条件
- 处理边界情况(空栈、剩余元素等)
5.2 常见错误与调试技巧
- 栈空判断缺失:在pop或peek前务必检查栈是否为空
- 索引计算错误:特别是在处理单调栈宽度计算时
- 哨兵节点遗忘:如柱状图问题中的首尾补零
- 调试方法:
- 打印栈内容在每次操作后
- 对简单测试用例手动模拟
- 检查边界条件(空输入、单元素等)
5.3 复杂度分析与优化
- 时间复杂度:大多数栈解法都是O(n),每个元素最多入栈出栈一次
- 空间复杂度:通常O(n),最坏情况下所有元素都在栈中
- 优化方向:
- 合并多个栈操作
- 用变量替代辅助栈(如最小栈可以用一个变量记录当前最小值)
- 使用位运算等技巧压缩存储
6. 扩展练习与资源推荐
6.1 推荐练习题目
| 题目 | 难度 | 关键点 |
|---|---|---|
| 下一个更大元素 I | 简单 | 基础单调栈 |
| 接雨水 | 困难 | 双指针与单调栈对比 |
| 最大矩形 | 困难 | 二维单调栈应用 |
| 滑动窗口最大值 | 困难 | 单调队列 |
| 去除重复字母 | 中等 | 栈与贪心结合 |
6.2 学习资源推荐
- 《算法导论》栈与队列章节
- LeetCode探索卡片"栈"和"单调栈"
- 可视化工具:VisuAlgo的栈演示
- 经典教材:《数据结构与算法分析》Mark Allen Weiss
6.3 面试准备建议
- 熟练掌握这5道题的各种变体
- 理解每种解法的适用场景和局限性
- 能够手写无bug的栈实现
- 准备复杂度分析和优化思路
- 练习用白板或在线编辑器清晰解释解题过程
在实际面试中,栈相关问题往往考察候选人对基础数据结构的理解深度和灵活应用能力。建议从暴力解法开始思考,逐步优化到栈解法,并清楚说明每一步的优化动机和效果。
