当前位置: 首页 > news >正文

栈队列 模版题单

栈和队列

简介

栈的特点是后入先出

根据这个特点可以临时保存一些数据,之后用到依次再弹出来,常用于 DFS 深度搜索

队列一般常用于 BFS 广度搜索,类似一层一层的搜索

Stack 栈

min-stack

设计一个支持 push,pop,top 操作,并能在常数时间内检索到最小元素的栈。

  • 思路:用两个栈实现或插入元组实现,保证当前最小值在栈顶即可
class MinStack:def __init__(self):self.stack = []def push(self, x: int) -> None:if len(self.stack) > 0:self.stack.append((x, min(x, self.stack[-1][1])))else:self.stack.append((x, x))def pop(self) -> int:return self.stack.pop()[0]def top(self) -> int:return self.stack[-1][0]def getMin(self) -> int:return self.stack[-1][1]

evaluate-reverse-polish-notation

波兰表达式计算 > 输入: [“2”, “1”, “+”, “3”, “*”] > 输出: 9
解释: ((2 + 1) * 3) = 9

  • 思路:通过栈保存原来的元素,遇到表达式弹出运算,再推入结果,重复这个过程
    这段代码实现了逆波兰表达式(RPN,也称为后缀表达式)的求值。逆波兰表达式的特点是运算符紧跟在操作数之后,例如 3 4 + 表示 3+4。解题的核心思路是使用栈来处理表达式,遇到操作数时入栈,遇到运算符时弹出操作数进行计算,并将结果压回栈中。

解题步骤解析

  1. 定义辅助计算函数 comp

    • 该函数接收两个操作数 or1or2 和一个运算符 op,返回计算结果。
    • 除法处理:使用整数除法并手动处理符号,确保结果向零截断(例如 -3 // 2 在 Python 中为 -2,而题目要求为 -1)。
  2. 初始化栈

    • 使用列表 stack 存储操作数。
  3. 遍历表达式中的每个 token

    • 若为操作数:直接转换为整数并入栈。
    • 若为运算符
      1. 从栈中弹出两个操作数(注意顺序:先弹出的是右操作数 or2,后弹出的是左操作数 or1)。
      2. 调用 comp 函数计算结果。
      3. 将计算结果压回栈中。
  4. 返回最终结果

    • 遍历结束后,栈中仅剩一个元素,即为表达式的值。

算法关键点

  • 栈的应用:栈的后进先出(LIFO)特性天然适合处理后缀表达式。遇到运算符时,最近的两个操作数一定在栈顶。
  • 操作数顺序:弹出操作数时,先弹出的是右操作数,后弹出的是左操作数(例如 3 4 - 表示 3-4,而非 4-3)。
  • 除法截断:手动实现向零截断的除法,确保结果符合题目要求。

示例说明

假设输入表达式为 ["2", "1", "+", "3", "*"](对应中缀表达式 (2+1)*3):

  1. 遍历过程

    • 遇到 2:入栈,栈为 [2]
    • 遇到 1:入栈,栈为 [2, 1]
    • 遇到 +:弹出 12,计算 2+1=3,入栈,栈为 [3]
    • 遇到 3:入栈,栈为 [3, 3]
    • 遇到 *:弹出 33,计算 3*3=9,入栈,栈为 [9]
  2. 结果:栈中仅剩 9,返回 9

复杂度分析

  • 时间复杂度:O(n)
    遍历每个 token 一次,每个操作(入栈、出栈、计算)均为 O(1)。
  • 空间复杂度:O(n)
    栈的最大深度为 n/2(当表达式中操作数和运算符交替出现时)。

总结

该算法通过栈高效地处理逆波兰表达式,确保每个运算符都能正确获取其操作数。关键在于理解栈与后缀表达式的匹配关系,以及对除法截断的特殊处理。这种方法简洁且高效,是处理后缀表达式的标准解法。

class Solution:def evalRPN(self, tokens: List[str]) -> int:def comp(or1, op, or2):if op == '+':return or1 + or2if op == '-':return or1 - or2if op == '*':return or1 * or2if op == '/':abs_result = abs(or1) // abs(or2)return abs_result if or1 * or2 > 0 else -abs_resultstack = []for token in tokens:if token in ['+', '-', '*', '/']:or2 = stack.pop()or1 = stack.pop()stack.append(comp(or1, token, or2))else:stack.append(int(token))return stack[0]

decode-string

给定一个经过编码的字符串,返回它解码后的字符串。
s = “3[a]2[bc]”, 返回 “aaabcbc”.
s = “3[a2[c]]”, 返回 “accaccacc”.
s = “2[abc]3[cd]ef”, 返回 “abcabccdcdcdef”.

  • 思路:通过两个栈进行操作,一个用于存数,另一个用来存字符串
class Solution:def decodeString(self, s: str) -> str:stack_str = ['']stack_num = []num = 0for c in s:if c >= '0' and c <= '9':num = num * 10 + int(c)elif c == '[':stack_num.append(num)stack_str.append('')num = 0elif c == ']':cur_str = stack_str.pop()stack_str[-1] += cur_str * stack_num.pop()else:stack_str[-1] += creturn stack_str[0]

binary-tree-inorder-traversal

给定一个二叉树,返回它的中序遍历。

  • reference
class Solution:def inorderTraversal(self, root: TreeNode) -> List[int]:stack, inorder = [], []node = rootwhile len(stack) > 0 or node is not None:if node is not None: stack.append(node)node = node.leftelse:node = stack.pop()inorder.append(node.val)node = node.rightreturn inorder

clone-graph

给你无向连通图中一个节点的引用,请你返回该图的深拷贝(克隆)。

  • BFS
    这段代码实现了图的深拷贝(克隆)功能,主要思路如下:
  1. 处理空图情况:如果输入的起始节点start为空,直接返回None

  2. 初始化访问字典和队列

    • visited字典用于记录已克隆的节点,键为原图节点,值为克隆节点。
    • 队列bfs用于广度优先搜索(BFS),初始包含起始节点start
  3. BFS遍历图

    • 出队当前节点:从队列中取出节点curr,获取其克隆节点curr_copy
    • 遍历邻接节点:对于curr的每个邻接节点n
      • 若未访问:创建n的克隆节点,存入visited,并将n加入队列待处理。
      • 建立克隆邻接关系:将n的克隆节点(无论是否新创建)添加到curr_copy的邻接列表中。
  4. 返回结果:最终返回起始节点的克隆节点visited[start]

关键点

  • 避免重复克隆:通过visited字典确保每个节点仅被克隆一次。
  • 邻接关系处理:在遍历过程中动态建立克隆节点间的邻接关系,保证图结构的正确性。

复杂度

  • 时间复杂度:O(N + M),N为节点数,M为边数。
  • 空间复杂度:O(N),主要用于存储克隆节点和BFS队列。
class Solution:def cloneGraph(self, start: 'Node') -> 'Node':if start is None:return Nonevisited = {start: Node(start.val, [])}bfs = collections.deque([start])while len(bfs) > 0:curr = bfs.popleft()curr_copy = visited[curr]for n in curr.neighbors:if n not in visited:visited[n] = Node(n.val, [])bfs.append(n)curr_copy.neighbors.append(visited[n])return visited[start]
  • DFS iterative
class Solution:def cloneGraph(self, start: 'Node') -> 'Node':if start is None:return Noneif not start.neighbors:return Node(start.val)visited = {start: Node(start.val, [])}dfs = [start]while len(dfs) > 0:peek = dfs[-1]peek_copy = visited[peek]if len(peek_copy.neighbors) == 0:for n in peek.neighbors:if n not in visited:visited[n] = Node(n.val, [])dfs.append(n)peek_copy.neighbors.append(visited[n])else:dfs.pop()return visited[start]

number-of-islands

给定一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,计算岛屿的数量。一个岛被水包围,并且它是通过水平方向或垂直方向上相邻的陆地连接而成的。你可以假设网格的四个边均被水包围。

High-level problem: number of connected component of graph

  • 思路:通过深度搜索遍历可能性(注意标记已访问元素)
class Solution:def numIslands(self, grid: List[List[str]]) -> int:if not grid or not grid[0]:return 0m, n = len(grid), len(grid[0])def dfs_iter(i, j):dfs = []dfs.append((i, j))while len(dfs) > 0:i, j = dfs.pop()if grid[i][j] == '1':grid[i][j] = '0'if i - 1 >= 0:dfs.append((i - 1, j))if j - 1 >= 0:dfs.append((i, j - 1))if i + 1 < m:dfs.append((i + 1, j))if j + 1 < n:dfs.append((i, j + 1))returnnum_island = 0for i in range(m):for j in range(n):if grid[i][j] == '1':num_island += 1dfs_iter(i, j)return num_island

largest-rectangle-in-histogram

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

  • 思路 1:蛮力法,比较每个以 i 开始 j 结束的最大矩形,A(i, j) = (j - i + 1) * min_height(i, j),时间复杂度 O(n^2) 无法 AC。
class Solution:def largestRectangleArea(self, heights: List[int]) -> int:max_area = 0n = len(heights)for i in range(n):min_height = heights[i]for j in range(i, n):min_height = min(min_height, heights[j])max_area = max(max_area, min_height * (j - i + 1))return max_area
  • 思路 2: 设 A(i, j) 为区间 [i, j) 内最大矩形的面积,k 为 [i, j) 内最矮 bar 的坐标,则 A(i, j) = max((j - i) * heights[k], A(i, k), A(k+1, j)), 使用分治法进行求解。时间复杂度 O(nlogn),其中使用简单遍历求最小值无法 AC (最坏情况退化到 O(n^2)),使用线段树优化后勉强 AC。
class Solution:def largestRectangleArea(self, heights: List[int]) -> int:n = len(heights)seg_tree = [None] * nseg_tree.extend(list(zip(heights, range(n))))for i in range(n - 1, 0, -1):seg_tree[i] = min(seg_tree[2 * i], seg_tree[2 * i + 1], key=lambda x: x[0])def _min(i, j):min_ = (heights[i], i)i += nj += nwhile i < j:if i % 2 == 1:min_ = min(min_, seg_tree[i], key=lambda x: x[0])i += 1if j % 2 == 1:j -= 1min_ = min(min_, seg_tree[j], key=lambda x: x[0])i //= 2j //= 2return min_def LRA(i, j):if i == j:return 0min_k, k = _min(i, j)return max(min_k * (j - i), LRA(k + 1, j), LRA(i, k))return LRA(0, n)
  • 思路 3:包含当前 bar 最大矩形的边界为左边第一个高度小于当前高度的 bar 和右边第一个高度小于当前高度的 bar。
class Solution:def largestRectangleArea(self, heights: List[int]) -> int:n = len(heights)stack = [-1]max_area = 0for i in range(n):while len(stack) > 1 and heights[stack[-1]] > heights[i]:h = stack.pop()max_area = max(max_area, heights[h] * (i - stack[-1] - 1))stack.append(i)while len(stack) > 1:h = stack.pop()max_area = max(max_area, heights[h] * (n - stack[-1] - 1))return max_area

Queue 队列

常用于 BFS 宽度优先搜索

implement-queue-using-stacks

使用栈实现队列

class MyQueue:def __init__(self):self.cache = []self.out = []def push(self, x: int) -> None:"""Push element x to the back of queue."""self.cache.append(x)def pop(self) -> int:"""Removes the element from in front of queue and returns that element."""if len(self.out) == 0:while len(self.cache) > 0:self.out.append(self.cache.pop())return self.out.pop() def peek(self) -> int:"""Get the front element."""if len(self.out) > 0:return self.out[-1]else:return self.cache[0]def empty(self) -> bool:"""Returns whether the queue is empty."""return len(self.cache) == 0 and len(self.out) == 0

binary-tree-level-order-traversal

二叉树的层序遍历

class Solution:def levelOrder(self, root: TreeNode) -> List[List[int]]:levels = []if root is None:return levelsbfs = collections.deque([root])while len(bfs) > 0:levels.append([])level_size = len(bfs)for _ in range(level_size):node = bfs.popleft()levels[-1].append(node.val)if node.left is not None:bfs.append(node.left)if node.right is not None:bfs.append(node.right)return levels

01-matrix

给定一个由 0 和 1 组成的矩阵,找出每个元素到最近的 0 的距离。
两个相邻元素间的距离为 1

  • 思路 1: 从 0 开始 BFS, 遇到距离最小值需要更新的则更新后重新入队更新后续结点
class Solution:def updateMatrix(self, matrix: List[List[int]]) -> List[List[int]]:if len(matrix) == 0 or len(matrix[0]) == 0:return matrixm, n = len(matrix), len(matrix[0])dist = [[float('inf')] * n for _ in range(m)]bfs = collections.deque([])for i in range(m):for j in range(n):if matrix[i][j] == 0:dist[i][j] = 0bfs.append((i, j))neighbors = [(-1, 0), (1, 0), (0, -1), (0, 1)]while len(bfs) > 0:i, j = bfs.popleft()for dn_i, dn_j in neighbors:n_i, n_j = i + dn_i, j + dn_jif n_i >= 0 and n_i < m and n_j >= 0 and n_j < n:if dist[n_i][n_j] > dist[i][j] + 1:dist[n_i][n_j] = dist[i][j] + 1bfs.append((n_i, n_j))return dist        
  • 思路 2: 2-pass DP,dist(i, j) = max{dist(i - 1, j), dist(i + 1, j), dist(i, j - 1), dist(i, j + 1)} + 1
class Solution:def updateMatrix(self, matrix: List[List[int]]) -> List[List[int]]:if len(matrix) == 0 or len(matrix[0]) == 0:return matrixm, n = len(matrix), len(matrix[0])dist = [[float('inf')] * n for _ in range(m)]for i in range(m):for j in range(n):if matrix[i][j] == 1:if i - 1 >= 0:dist[i][j] = min(dist[i - 1][j] + 1, dist[i][j])if j - 1 >= 0:dist[i][j] = min(dist[i][j - 1] + 1, dist[i][j])else:dist[i][j] = 0for i in range(-1, -m - 1, -1):for j in range(-1, -n - 1, -1):if matrix[i][j] == 1:if i + 1 < 0:dist[i][j] = min(dist[i + 1][j] + 1, dist[i][j])if j + 1 < 0:dist[i][j] = min(dist[i][j + 1] + 1, dist[i][j])return dist

补充:单调栈

顾名思义,单调栈即是栈中元素有单调性的栈,典型应用为用线性的时间复杂度找左右两侧第一个大于/小于当前元素的位置。

largest-rectangle-in-histogram

这段代码使用单调栈解决了"柱状图中最大矩形面积"问题,其核心思路如下:

问题分析

给定一组柱状图的高度,要求找到其中能够形成的最大矩形面积。每个柱子的宽度为1,矩形需基于柱子的高度。

算法思路

  1. 单调递增栈:维护一个栈,存储柱子的索引,确保栈中索引对应的高度单调递增。当遇到较小高度时,弹出栈顶元素并计算面积。
  2. 虚拟尾元素:在高度数组末尾添加一个0,确保所有柱子都能被弹出栈(包括最后一个非零高度的柱子)。
  3. 虚拟头元素:栈初始化为[-1],作为边界处理,避免栈为空的情况。
  4. 遍历与弹栈
    • 遍历每个柱子,若当前高度小于栈顶高度,弹出栈顶元素并计算其面积。
    • 宽度计算:当前索引i与新的栈顶索引stack[-1]之间的距离减1(即i - stack[-1] - 1)。
    • 更新最大面积。
  5. 最终结果:遍历结束后,栈中元素全部弹出,得到全局最大面积。

关键步骤解释

  • 单调栈的作用:确保栈中元素的高度递增,遇到较小高度时触发弹栈,计算以弹出元素为高的矩形面积(因为左右边界已确定)。
  • 宽度计算:对于弹出的元素cur,其右边界为当前索引i,左边界为新的栈顶stack[-1],宽度为i - stack[-1] - 1
  • 时间复杂度:每个元素最多入栈和出栈一次,时间复杂度为O(n)。

示例演示

假设输入为[2, 1, 5, 6, 2, 3],添加尾部0后变为[2, 1, 5, 6, 2, 3, 0]

  1. 遍历到i=1(高度1):弹出2,面积为2 * (1 - (-1) - 1) = 2
  2. 遍历到i=4(高度2):弹出65,面积分别为6 * (4 - 2 - 1) = 65 * (4 - 1 - 1) = 10
  3. 遍历到i=6(高度0):弹出所有剩余元素,计算面积3 * (6 - 4 - 1) = 32 * (6 - 3 - 1) = 41 * (6 - (-1) - 1) = 6
  4. 最大面积为10,对应高度5和宽度2的矩形。

总结

通过单调栈维护递增高度的索引,遇到较小高度时触发计算,确保每个柱子的最大可能矩形被遍历到。该方法高效且优雅,是解决此类问题的经典思路。

class Solution:def largestRectangleArea(self, heights) -> int:heights.append(0)stack = [-1]result = 0for i in range(len(heights)):while stack and heights[i] < heights[stack[-1]]:cur = stack.pop()result = max(result, heights[cur] * (i - stack[-1] - 1))stack.append(i)return result

trapping-rain-water

class Solution:def trap(self, height: List[int]) -> int:stack = []result = 0for i in range(len(height)):while stack and height[i] > height[stack[-1]]:cur = stack.pop()if not stack:breakresult += (min(height[stack[-1]], height[i]) - height[cur]) * (i - stack[-1] - 1)stack.append(i)return result

补充:单调队列

单调栈的拓展,可以从数组头 pop 出旧元素,典型应用是以线性时间获得区间最大/最小值。

sliding-window-maximum

求滑动窗口中的最大元素

代码思路解析

这段代码实现了一个滑动窗口最大值算法,即在给定数组 nums 和窗口大小 k 的情况下,返回每个窗口的最大值组成的列表。以下是详细的思路分析:


1. 特殊情况处理
N = len(nums)
if N * k == 0:return []if k == 1:return nums[:]
  • N * k == 0:如果数组为空(N=0)或窗口大小为 0(k=0),直接返回空列表。
  • k == 1:如果窗口大小为 1,每个窗口的最大值就是该元素本身,直接返回原数组的拷贝。

2. 使用双端队列维护最大值索引
maxQ = collections.deque()
result = []
  • maxQ:一个双端队列(deque),用于存储当前窗口内可能成为最大值的元素的索引(而不是值)。
  • result:存储最终结果的列表。

3. 遍历数组,维护双端队列
for i in range(N):# 移除窗口外的元素if maxQ and maxQ[0] == i - k:maxQ.popleft()# 移除队列中比当前元素小的元素while maxQ and nums[maxQ[-1]] < nums[i]:maxQ.pop()# 将当前元素索引加入队列maxQ.append(i)# 当窗口形成时,记录当前窗口的最大值if i >= k - 1:result.append(nums[maxQ[0]])
(1) 移除窗口外的元素
if maxQ and maxQ[0] == i - k:maxQ.popleft()
  • maxQ[0] 是当前窗口最大值的索引。
  • 如果 maxQ[0] == i - k,说明该索引已经不在当前窗口范围内(窗口范围为 [i-k+1, i]),需要从队列头部移除。
(2) 移除队列中比当前元素小的元素
while maxQ and nums[maxQ[-1]] < nums[i]:maxQ.pop()
  • 从队列尾部开始,移除所有比当前元素 nums[i] 小的元素的索引。
  • 因为这些元素在后续窗口中不可能成为最大值(当前元素 nums[i] 比它们大且存活时间更长)。
  • 这样可以保证队列是单调递减的(队头是当前窗口的最大值)。
(3) 将当前元素索引加入队列
maxQ.append(i)
  • 将当前索引 i 加入队列尾部。
(4) 记录当前窗口的最大值
if i >= k - 1:result.append(nums[maxQ[0]])
  • i >= k - 1 时,窗口已经形成(从索引 0 开始,前 k-1 个元素不足以形成窗口)。
  • 此时队列头部 maxQ[0] 就是当前窗口的最大值的索引,将其值 nums[maxQ[0]] 加入结果列表。

4. 返回结果

return result
  • 最终返回所有窗口的最大值列表。

示例演示

nums = [1, 3, -1, -3, 5, 3, 6, 7]k = 3 为例:

  1. 初始化maxQ = [], result = []
  2. 遍历过程
    • i=0maxQ = [0](值 1),窗口未形成。
    • i=1:移除 nums[0]=1 < nums[1]=3maxQ = [1](值 3),窗口未形成。
    • i=2:移除 nums[1]=3 > nums[2]=-1maxQ = [1, 2](值 3, -1),窗口形成,result = [3]
    • i=3maxQ[0] == 0i-k=0),移除 maxQ[0]maxQ = [2]nums[2]=-1 > nums[3]=-3maxQ = [2, 3](值 -1, -3),result = [3, 3]
    • i=4:移除 nums[2]=-1 < nums[4]=5nums[3]=-3 < 5maxQ = [4](值 5),result = [3, 3, 5]
    • i=5nums[4]=5 > nums[5]=3maxQ = [4, 5](值 5, 3),result = [3, 3, 5, 5]
    • i=6:移除 nums[4]=5 < nums[6]=6nums[5]=3 < 6maxQ = [6](值 6),result = [3, 3, 5, 5, 6]
    • i=7:移除 nums[6]=6 < nums[7]=7maxQ = [7](值 7),result = [3, 3, 5, 5, 6, 7]
  3. 最终结果[3, 3, 5, 5, 6, 7]

时间复杂度分析

  • 时间复杂度O(N),每个元素最多被加入和移除队列一次。
  • 空间复杂度O(k),双端队列最多存储 k 个元素。

这是一种高效的滑动窗口最大值算法!

class Solution:def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:N = len(nums)if N * k == 0:return []if k == 1:return nums[:]# define a max queuemaxQ = collections.deque()result = []for i in range(N):if maxQ and maxQ[0] == i - k:maxQ.popleft()while maxQ and nums[maxQ[-1]] < nums[i]:maxQ.pop()maxQ.append(i)if i >= k - 1:result.append(nums[maxQ[0]])return result

shortest-subarray-with-sum-at-least-k

class Solution:def shortestSubarray(self, A: List[int], K: int) -> int:N = len(A)cdf = [0]for num in A:cdf.append(cdf[-1] + num)result = N + 1minQ = collections.deque()for i, csum in enumerate(cdf):while minQ and csum <= cdf[minQ[-1]]:minQ.pop()while minQ and csum - cdf[minQ[0]] >= K:result = min(result, i - minQ.popleft())minQ.append(i)return result if result < N + 1 else -1 

总结

  • 熟悉栈的使用场景
    • 后入先出,保存临时值
    • 利用栈 DFS 深度搜索
  • 熟悉队列的使用场景
    • 利用队列 BFS 广度搜索

练习

  • min-stack
  • evaluate-reverse-polish-notation
  • decode-string
  • binary-tree-inorder-traversal
  • clone-graph
  • number-of-islands
  • largest-rectangle-in-histogram
  • implement-queue-using-stacks
  • 01-matrix

相关文章:

  • 洛谷 P1800 software(DP+二分)【提高+/省选−】
  • 电子电路:深入理解电磁耦合的定义与应用
  • Redis删除策略
  • 常见的gittee开源项目推荐
  • 【Elasticsearch】创建别名的几种方式
  • [linux] git强行拉取并覆盖
  • 2025电工杯数学建模竞赛A题 光伏电站发电功率日前预测问题 完整论文+python代码发布!
  • P1217 [USACO1.5] 回文质数 Prime Palindromes
  • CAN XL如何填平车载网络的“带宽鸿沟”?
  • MoE架构分析
  • K3S集群使用自签署证书拉取私有仓库镜像
  • MySQL强化关键_015_存储过程
  • redis 进行缓存实战-18
  • np.linspace() 简介
  • NLweb本地部署指南
  • JavaScript 语句标识符详解
  • 【信息系统项目管理师】第18章:项目绩效域 - 45个经典题目及详解
  • 20250523-关于Unity中的GUID简介(未完待续)
  • 车载诊断架构 --- 车载诊断有那些内容(上)
  • 解决Vue项目依赖错误:使用electron-vite重建
  • 上海市住房和城乡建设管理局网站/百度官网认证多少钱
  • 高密住房和城乡建设局网站/怎样才能在百度上面做广告宣传
  • 巨野做网站的/网站收录
  • 大学院系网站建设/北京知名seo公司精准互联
  • 怎么做类似淘宝一样的网站/网站宣传推广方案
  • 找别人做网站可以提供源码吗/短视频营销策划方案