栈与队列通关八题:从括号匹配到接雨水,手撕LeetCode高频题(Python + C++)

栈与队列通关八题:从括号匹配到接雨水,手撕LeetCode高频题(Python + C++)

栈和队列是数据结构的基础,也是面试笔试的高频考点。本文整理了8道经典题目,每道题都包含:题目描述、解题思路、图解(文本示意)、Python代码、C++代码。全部理解后,栈和队列类题目基本通关。


📌 题目清单

题号 题目 核心考点
20 有效的括号 栈匹配
155 最小栈 辅助栈 / 差值法
150 逆波兰表达式求值 栈计算
232 用栈实现队列 双栈模拟
239 滑动窗口最大值 单调队列(双端队列)
739 每日温度 单调栈(下一个更大元素)
84 柱状图中最大的矩形 单调栈 + 哨兵
42 接雨水 单调栈 / 双指针

1. 有效的括号(LeetCode 20)

题目描述

给定一个只包含 '(', ')', '{', '}', '[', ']' 的字符串,判断字符串是否有效。
有效条件

  • 左括号必须用相同类型的右括号闭合。
  • 左括号必须以正确的顺序闭合。
  • 每个右括号都有一个对应的相同类型的左括号。

示例

输入:"()[]{}" → 输出:true

输入:"([)]" → 输出:false

解题思路

  • 遍历字符串,遇到左括号则压入栈。
  • 遇到右括号时,检查栈顶是否为对应的左括号:
    • 如果是,弹出栈顶。
    • 如果不是或栈为空,返回 false
  • 遍历结束后,栈为空则有效。

图解

复制代码
字符串: ( [ ] { } )
栈变化:
( → 栈: [
( [ → 栈: [ [, [
遇到 ] → 栈顶是 [,匹配,弹出 → 栈: [
遇到 } → 栈顶是 {,匹配,弹出 → 栈: ( 
遇到 ) → 栈顶是 (,匹配,弹出 → 栈: 空
最终栈空 → true

Python代码

python 复制代码
def isValid(s: str) -> bool:
    mapping = {')': '(', ']': '[', '}': '{'}
    stack = []
    for ch in s:
        if ch in mapping:
            if not stack or stack[-1] != mapping[ch]:
                return False
            stack.pop()
        else:
            stack.append(ch)
    return not stack

C++代码

cpp 复制代码
class Solution {
public:
    bool isValid(string s) {
        stack<char> st;
        unordered_map<char, char> mapping = {{')', '('}, {']', '['}, {'}', '{'}};
        for (char ch : s) {
            if (mapping.count(ch)) {
                if (st.empty() || st.top() != mapping[ch]) return false;
                st.pop();
            } else {
                st.push(ch);
            }
        }
        return st.empty();
    }
};

2. 最小栈(LeetCode 155)

题目描述

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

实现 MinStack 类:

  • push(val) 将元素压入栈
  • pop() 删除栈顶元素
  • top() 获取栈顶元素
  • getMin() 获取栈中的最小元素

解题思路:辅助栈

维护一个数据栈 st 和一个最小栈 minStminSt 的栈顶始终存储当前数据栈中的最小值。

  • push:数据栈直接压入,最小栈压入当前最小值(min(当前值, minSt栈顶)
  • pop:两个栈同时弹出
  • getMin:返回 minSt 栈顶

图解

复制代码
操作: push 3, push 5, push 2, getMin, pop, getMin
数据栈:    3         3,5       3,5,2     3,5       3
最小栈:    3         3,3       3,3,2     3,3       3
getMin: 3 -> 3 -> 2 -> 2 -> 3

Python代码

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]

C++代码

cpp 复制代码
class MinStack {
    stack<int> st;
    stack<int> minSt;
public:
    MinStack() {}
    
    void push(int val) {
        st.push(val);
        if (minSt.empty() || val <= minSt.top()) minSt.push(val);
    }
    
    void pop() {
        if (st.top() == minSt.top()) minSt.pop();
        st.pop();
    }
    
    int top() { return st.top(); }
    
    int getMin() { return minSt.top(); }
};

3. 逆波兰表达式求值(LeetCode 150)

题目描述

给你一个字符串数组 tokens ,表示一个逆波兰表达式,求表达式的值。

有效运算符为 '+''-''*''/'。除法向零截断。

示例

输入:tokens = ["2","1","+","3","*"]

输出:9

解释:((2 + 1) * 3) = 9

解题思路

  • 遍历 tokens,遇到数字压栈。
  • 遇到运算符,弹出两个数字(先弹出右操作数,后弹出左操作数),计算结果后压回栈。
  • 最后栈顶即为结果。

图解

复制代码
tokens: 2, 1, +, 3, *
栈:
2 → [2]
1 → [2,1]
+ → 弹出1,2 → 2+1=3 → 压3 → [3]
3 → [3,3]
* → 弹出3,3 → 3*3=9 → [9]
结果:9

Python代码

python 复制代码
def evalRPN(tokens):
    stack = []
    for t in tokens:
        if t in {"+", "-", "*", "/"}:
            b = stack.pop()
            a = stack.pop()
            if t == "+": stack.append(a + b)
            elif t == "-": stack.append(a - b)
            elif t == "*": stack.append(a * b)
            else: stack.append(int(a / b))  # 向零截断
        else:
            stack.append(int(t))
    return stack[0]

C++代码

cpp 复制代码
class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        stack<int> st;
        for (string& t : tokens) {
            if (t == "+" || t == "-" || t == "*" || t == "/") {
                int b = st.top(); st.pop();
                int a = st.top(); st.pop();
                if (t == "+") st.push(a + b);
                else if (t == "-") st.push(a - b);
                else if (t == "*") st.push(a * b);
                else st.push(a / b);
            } else {
                st.push(stoi(t));
            }
        }
        return st.top();
    }
};

4. 用栈实现队列(LeetCode 232)

题目描述

请你仅使用两个栈实现先入先出队列。实现 MyQueue 类:

  • push(x) 将元素 x 推到队列的末尾
  • pop() 从队列的开头移除并返回元素
  • peek() 返回队列开头的元素
  • empty() 判断队列是否为空

解题思路:双栈模拟

  • 输入栈 inSt 用于 push
  • 输出栈 outSt 用于 poppeek
  • outSt 为空时,将 inSt 的所有元素弹出并压入 outSt,这样 outSt 的栈顶就是队首元素。

图解

复制代码
push 1,2,3:
inSt: [1,2,3] (栈顶3), outSt: []
pop:
outSt为空 → 将inSt全部移到outSt:
inSt: [], outSt: [3,2,1] (栈顶1)
pop弹出1 → outSt: [3,2]

Python代码

python 复制代码
class MyQueue:
    def __init__(self):
        self.in_st = []
        self.out_st = []

    def push(self, x: int) -> None:
        self.in_st.append(x)

    def pop(self) -> int:
        self._move()
        return self.out_st.pop()

    def peek(self) -> int:
        self._move()
        return self.out_st[-1]

    def empty(self) -> bool:
        return not self.in_st and not self.out_st

    def _move(self):
        if not self.out_st:
            while self.in_st:
                self.out_st.append(self.in_st.pop())

C++代码

cpp 复制代码
class MyQueue {
    stack<int> inSt, outSt;
    void move() {
        if (outSt.empty()) {
            while (!inSt.empty()) {
                outSt.push(inSt.top());
                inSt.pop();
            }
        }
    }
public:
    void push(int x) { inSt.push(x); }
    int pop() {
        move();
        int val = outSt.top(); outSt.pop();
        return val;
    }
    int peek() {
        move();
        return outSt.top();
    }
    bool empty() { return inSt.empty() && outSt.empty(); }
};

5. 滑动窗口最大值(LeetCode 239)

题目描述

给定一个整数数组 nums 和一个大小为 k 的滑动窗口,窗口从数组最左端移动到最右端,每次向右移动一位。返回每个窗口中的最大值。

示例

输入:nums = [1,3,-1,-3,5,3,6,7], k = 3

输出:[3,3,5,5,6,7]

解题思路:单调队列(双端队列)

  • 使用双端队列 deque 存储数组的下标 ,保证队列中下标对应的值单调递减
  • 遍历数组:
    • 移除队列中已经滑出窗口的下标(i - deque[0] >= k
    • 从队尾移除所有比当前元素小的元素(因为它们不可能成为后续窗口的最大值)
    • 将当前下标加入队尾
    • i >= k-1 时,队首元素就是当前窗口的最大值

图解

复制代码
nums: [1,3,-1,-3,5,3,6,7], k=3
i=0: deque=[0] (1)
i=1: 3>1 → 弹出0 → deque=[1] (3)
i=2: -1<3 → 加入2 → deque=[1,2] (3,-1) → 窗口[0-2]最大值3
i=3: -3<-1 → 加入3 → deque=[1,2,3] → 但1未出窗口(i=3,窗口[1-3])max=3
i=4: 5>队尾-3 → 弹出3,2 → 5>3 → 弹出1 → deque=[4] (5) → 最大值5
...

Python代码

python 复制代码
from collections import deque

def maxSlidingWindow(nums, k):
    dq = deque()
    res = []
    for i, num in enumerate(nums):
        # 移除滑出窗口的索引
        if dq and dq[0] <= i - k:
            dq.popleft()
        # 移除队尾所有小于当前值的
        while dq and nums[dq[-1]] < num:
            dq.pop()
        dq.append(i)
        if i >= k - 1:
            res.append(nums[dq[0]])
    return res

C++代码

cpp 复制代码
class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        deque<int> dq;
        vector<int> res;
        for (int i = 0; i < nums.size(); ++i) {
            if (!dq.empty() && dq.front() <= i - k) dq.pop_front();
            while (!dq.empty() && nums[dq.back()] < nums[i]) dq.pop_back();
            dq.push_back(i);
            if (i >= k - 1) res.push_back(nums[dq.front()]);
        }
        return res;
    }
};

6. 每日温度(LeetCode 739)

题目描述

给定一个整数数组 temperatures,表示每天的温度,返回一个数组 answer,其中 answer[i] 是指对于第 i 天,下一个更高温度出现在几天后。如果之后没有更高温度,则为 0

示例

输入:temperatures = [73,74,75,71,69,72,76,73]

输出:[1,1,4,2,1,1,0,0]

解题思路:单调栈(下一个更大元素)

  • 维护一个单调递减栈(栈底到栈顶递减),栈中存储下标。
  • 遍历温度数组:
    • 当当前温度 > 栈顶下标对应的温度时,说明找到了栈顶元素的下一个更高温度,计算距离并弹出栈顶。
    • 将当前下标压入栈。
  • 最后栈中剩余下标对应结果为0。

图解

复制代码
temperatures: 73,74,75,71,69,72,76,73
i=0: 栈[0] (73)
i=1: 74>73 → 弹出0 → ans[0]=1-0=1 → 栈[1] (74)
i=2: 75>74 → 弹出1 → ans[1]=1 → 栈[2] (75)
i=3: 71<75 → 压3 → 栈[2,3] (75,71)
i=4: 69<71 → 压4 → 栈[2,3,4]
i=5: 72>69 → 弹出4 → ans[4]=1 → 72>71 → 弹出3 → ans[3]=2 → 72<75 → 压5 → 栈[2,5]
i=6: 76>72 → 弹出5 → ans[5]=1 → 76>75 → 弹出2 → ans[2]=4 → 栈[6]
i=7: 73<76 → 压7 → 结束
ans: [1,1,4,2,1,1,0,0]

Python代码

python 复制代码
def dailyTemperatures(temperatures):
    n = len(temperatures)
    ans = [0] * n
    stack = []
    for i, t in enumerate(temperatures):
        while stack and temperatures[stack[-1]] < t:
            idx = stack.pop()
            ans[idx] = i - idx
        stack.append(i)
    return ans

C++代码

cpp 复制代码
class Solution {
public:
    vector<int> dailyTemperatures(vector<int>& temperatures) {
        int n = temperatures.size();
        vector<int> ans(n, 0);
        stack<int> st;
        for (int i = 0; i < n; ++i) {
            while (!st.empty() && temperatures[st.top()] < temperatures[i]) {
                int idx = st.top(); st.pop();
                ans[idx] = i - idx;
            }
            st.push(i);
        }
        return ans;
    }
};

7. 柱状图中最大的矩形(LeetCode 84)

题目描述

给定 n 个非负整数,表示柱状图中各个柱子的高度,每个柱子宽度为1。求柱状图中能勾勒出的最大矩形面积。

示例

输入:heights = [2,1,5,6,2,3]

输出:10(高度5和6的矩形面积5×2=10)

解题思路:单调栈 + 哨兵

  • 维护一个单调递增栈(栈底到栈顶高度递增),存储下标。
  • 遍历数组,当遇到高度小于栈顶高度时,说明找到了栈顶柱子的右边界,弹出栈顶并计算以该高度为高的矩形面积(宽度 = 当前下标 - 弹出后的新栈顶下标 - 1)。
  • 为了处理所有柱子,在数组前后各加一个高度为0的哨兵,保证最后栈被清空。

图解

复制代码
heights: [2,1,5,6,2,3]
前后加哨兵: [0,2,1,5,6,2,3,0]
i=0: 0入栈 [0]
i=1: 2>0 → 入1 [0,1]
i=2: 1<2 → 弹出1 → 高度2,左边界0,右边界2 → 面积2*(2-0-1)=2 → 栈[0] → 1>0 → 入2 [0,2]
i=3: 5>1 → 入3 [0,2,3]
i=4: 6>5 → 入4 [0,2,3,4]
i=5: 2<6 → 弹出4 → 高6,左边界3,右边界5 → 面积6*(5-3-1)=6 → 2<5 → 弹出3 → 高5,左边界2,右边界5 → 面积5*(5-2-1)=10 → 2>1 → 入5 [0,2,5]
...继续
最大面积10

Python代码

python 复制代码
def largestRectangleArea(heights):
    heights = [0] + heights + [0]
    stack = []
    max_area = 0
    for i, h in enumerate(heights):
        while stack and heights[stack[-1]] > h:
            height = heights[stack.pop()]
            left = stack[-1] if stack else -1
            width = i - left - 1
            max_area = max(max_area, height * width)
        stack.append(i)
    return max_area

C++代码

cpp 复制代码
class Solution {
public:
    int largestRectangleArea(vector<int>& heights) {
        heights.insert(heights.begin(), 0);
        heights.push_back(0);
        stack<int> st;
        int maxArea = 0;
        for (int i = 0; i < heights.size(); ++i) {
            while (!st.empty() && heights[st.top()] > heights[i]) {
                int h = heights[st.top()]; st.pop();
                int left = st.empty() ? -1 : st.top();
                int width = i - left - 1;
                maxArea = max(maxArea, h * width);
            }
            st.push(i);
        }
        return maxArea;
    }
};

8. 接雨水(LeetCode 42)

题目描述

给定 n 个非负整数表示每个宽度为1的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

示例

输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]

输出:6

解题思路:双指针(推荐,O(n)时间 O(1)空间)

  • 使用左右指针 left, right 和左右最大高度 left_max, right_max
  • 每次移动较小高度的一侧,计算当前柱子能接的雨水并累加。

原理 :对于位置 i,能接的雨水量 = min(左边最大值, 右边最大值) - height[i]。双指针通过维护 left_maxright_max 来避免额外数组。

图解

复制代码
height: 0,1,0,2,1,0,1,3,2,1,2,1
left=0, right=11, left_max=0, right_max=1
left_max<right_max → 处理left: left_max=max(0,0)=0, water+=0-0=0, left=1
left=1, left_max=1, right_max=1 → 相等可处理任意,比如处理left: left_max=1, water+=1-1=0, left=2
left=2, left_max=1, right_max=1 → 处理left: height[2]=0, water+=1-0=1, left=3
left=3, left_max=2, right_max=1 → left_max>right_max → 处理right: height[11]=1, right_max=1, water+=1-1=0, right=10
...最终得到6

Python代码

python 复制代码
def trap(height):
    left, right = 0, len(height) - 1
    left_max = right_max = 0
    water = 0
    while left < right:
        if height[left] < height[right]:
            if height[left] >= left_max:
                left_max = height[left]
            else:
                water += left_max - height[left]
            left += 1
        else:
            if height[right] >= right_max:
                right_max = height[right]
            else:
                water += right_max - height[right]
            right -= 1
    return water

C++代码

cpp 复制代码
class Solution {
public:
    int trap(vector<int>& height) {
        int left = 0, right = height.size() - 1;
        int leftMax = 0, rightMax = 0;
        int water = 0;
        while (left < right) {
            if (height[left] < height[right]) {
                if (height[left] >= leftMax) leftMax = height[left];
                else water += leftMax - height[left];
                ++left;
            } else {
                if (height[right] >= rightMax) rightMax = height[right];
                else water += rightMax - height[right];
                --right;
            }
        }
        return water;
    }
};

好的,在原表格基础上补充空间复杂度列,使总结更完整。


🎯 栈与队列八题总结(含空间复杂度)

题目 核心技巧 时间复杂度 空间复杂度
20. 有效的括号 栈匹配 O(n) O(n)(栈最坏存所有左括号)
155. 最小栈 辅助栈存储最小值 O(1) per op O(n)(辅助栈最坏存所有元素)
150. 逆波兰表达式 栈计算 O(n) O(n)(栈深度等于表达式嵌套深度)
232. 用栈实现队列 双栈(输入输出) 均摊O(1) O(n)(两个栈总存所有元素)
239. 滑动窗口最大值 单调队列(双端队列) O(n) O(k)(双端队列最多存k个下标,k为窗口大小)
739. 每日温度 单调栈(下一个更大元素) O(n) O(n)(栈最坏存所有下标)
84. 柱状图最大矩形 单调栈 + 哨兵 O(n) O(n)(栈存下标,最坏情况)
42. 接雨水 双指针 / 单调栈 O(n) O(1)(双指针法)/ O(n)(单调栈法)

说明

  • 最小栈的辅助栈法空间 O(n),差值法可优化至 O(1) 但较复杂。
  • 滑动窗口最大值的单调队列空间为 O(k),优于 O(n)。
  • 接雨水双指针法空间 O(1) 最优,推荐掌握。
    栈和队列的题目往往有固定模式:括号匹配、单调栈、单调队列。多画图、多模拟,理解为什么使用栈/队列以及单调性如何维护,就能举一反三。
相关推荐
dFObBIMmai1 小时前
MongoDB防注入攻击指南
jvm·数据库·python
彳亍1011 小时前
如何解决Oracle启动ORA-00119错误_网络服务名与listener相关性
jvm·数据库·python
weixin_459753941 小时前
c++怎么编写多线程安全的跨平台文件日志库_无锁队列与异步IO【附源码】
jvm·数据库·python
夏恪1 小时前
如何用 IDBKeyRange 范围匹配检索特定区间的本地数据
jvm·数据库·python
风筝在晴天搁浅1 小时前
字节 LeetCode CodeTop 912.排序数组
算法·leetcode
2301_766283441 小时前
如何防止SQL拼接漏洞_使用PDO对象实现安全的SQL交互
jvm·数据库·python
u0110225121 小时前
如何解决Oracle 12c以上版本的ORA-65096_C##公共用户前缀限制
jvm·数据库·python
Byron Loong1 小时前
【逆向】AT Hook 与 Inline Hook 对比
c语言·汇编·c++
woxihuan1234561 小时前
JavaScript中利用Range对象实现复杂的文本选择操作
jvm·数据库·python