备战蓝桥杯--栈

本文基于C++语言,在备战蓝桥杯算法竞赛过程中,通过对力扣Hot100栈相关题型的刷题和总结,归纳出栈的核心知识点、常用技巧和实战经验。后续也会持续更新具体题目解析,欢迎关注!

一、栈的基本概念与特性

1.1 什么是栈?

栈是一种后进先出(LIFO, Last In First Out) 的线性数据结构,只允许在栈顶进行插入和删除操作。想象一下生活中的叠盘子------你总是取最上面的盘子(最后放上去的),这就是栈的工作原理。

1.2 栈的ADT(抽象数据类型)

cpp 复制代码
template <typename T>
class Stack {
public:
    bool empty() const;      // 判断栈是否为空
    size_t size() const;     // 返回栈中元素个数
    T& top();                // 返回栈顶元素引用
    const T& top() const;    // 返回栈顶元素常量引用
    void push(const T& val); // 入栈
    void pop();              // 出栈
};

1.3 栈的实现方式对比

实现方式 优点 缺点 适用场景
数组实现 内存连续,访问快 大小固定,可能溢出 已知最大容量
向量(vector)实现 动态扩容,使用方便 扩容时性能开销 通用场景
链表实现 动态内存,无溢出 内存开销大,访问慢 频繁插入删除

二、栈的核心应用场景

2.1 括号匹配 - 栈的经典应用

cpp 复制代码
bool isValidParentheses(const string& s) {
    stack<char> stk;
    unordered_map<char, char> pairs = {
        {')', '('}, {']', '['}, {'}', '{'}
    };
    
    for (char c : s) {
        if (c == '(' || c == '[' || c == '{') {
            stk.push(c);
        } else if (c == ')' || c == ']' || c == '}') {
            // 栈为空或括号不匹配
            if (stk.empty() || stk.top() != pairs[c]) {
                return false;
            }
            stk.pop();
        }
    }
    return stk.empty();  // 栈必须为空才算完全匹配
}

要点分析:

  • 时间复杂度:O(n),只需一次遍历

  • 空间复杂度:O(n),最坏情况下所有字符都入栈

  • 关键点:遇到左括号入栈,遇到右括号检查栈顶是否匹配

2.2 表达式求值

cpp 复制代码
int evaluateExpression(const string& expr) {
    stack<int> nums;      // 数字栈
    stack<char> ops;      // 运算符栈
    
    auto calculate = [&]() {
        if (nums.size() < 2 || ops.empty()) return;
        int b = nums.top(); nums.pop();
        int a = nums.top(); nums.pop();
        char op = ops.top(); ops.pop();
        
        switch(op) {
            case '+': nums.push(a + b); break;
            case '-': nums.push(a - b); break;
            case '*': nums.push(a * b); break;
            case '/': nums.push(a / b); break;
        }
    };
    
    unordered_map<char, int> precedence = {
        {'+', 1}, {'-', 1}, {'*', 2}, {'/', 2}
    };
    
    for (int i = 0; i < expr.length(); i++) {
        if (expr[i] == ' ') continue;
        
        if (isdigit(expr[i])) {
            int num = 0;
            while (i < expr.length() && isdigit(expr[i])) {
                num = num * 10 + (expr[i] - '0');
                i++;
            }
            i--;  // 回退一步
            nums.push(num);
        } else if (expr[i] == '(') {
            ops.push(expr[i]);
        } else if (expr[i] == ')') {
            while (!ops.empty() && ops.top() != '(') {
                calculate();
            }
            if (!ops.empty()) ops.pop();  // 弹出'('
        } else {
            // 当前操作符优先级 <= 栈顶操作符优先级时,先计算
            while (!ops.empty() && ops.top() != '(' && 
                   precedence[expr[i]] <= precedence[ops.top()]) {
                calculate();
            }
            ops.push(expr[i]);
        }
    }
    
    while (!ops.empty()) {
        calculate();
    }
    
    return nums.top();
}

2.3 单调栈 - 解决"下一个更大元素"问题

cpp 复制代码
vector<int> nextGreaterElements(const vector<int>& nums) {
    int n = nums.size();
    vector<int> result(n, -1);
    stack<int> stk;  // 存储索引
    
    for (int i = 0; i < n * 2; i++) {
        int idx = i % n;
        while (!stk.empty() && nums[stk.top()] < nums[idx]) {
            result[stk.top()] = nums[idx];
            stk.pop();
        }
        // 只在实际的数组范围内入栈
        if (i < n) {
            stk.push(i);
        }
    }
    return result;
}

单调栈要点:

  • 保持栈内元素单调递减(或递增)

  • 常用于:下一个更大/小元素、最大矩形面积、接雨水等问题

  • 时间复杂度:O(n),每个元素最多入栈出栈一次

三、C++ STL栈的使用详解

3.1 基本操作

cpp 复制代码
#include <stack>
#include <iostream>
#include <vector>

void stackDemo() {
    // 1. 创建栈
    stack<int> s1;                     // 默认使用deque作为底层容器
    stack<int, vector<int>> s2;        // 使用vector作为底层容器
    
    // 2. 入栈操作
    s1.push(1);
    s1.push(2);
    s1.push(3);
    s1.emplace(4);  // 直接构造,C++11引入
    
    // 3. 访问栈顶
    cout << "栈顶元素: " << s1.top() << endl;  // 输出4
    
    // 4. 出栈
    s1.pop();  // 移除4,不返回值
    
    // 5. 容量查询
    cout << "栈大小: " << s1.size() << endl;
    cout << "是否为空: " << (s1.empty() ? "是" : "否") << endl;
    
    // 6. 遍历栈(注意:栈没有迭代器,只能边pop边访问)
    cout << "栈内容: ";
    while (!s1.empty()) {
        cout << s1.top() << " ";
        s1.pop();
    }
    cout << endl;
}

3.2 实战技巧:栈的最小值问题

cpp 复制代码
class MinStack {
private:
    stack<int> dataStack;  // 数据栈
    stack<int> minStack;   // 辅助栈,存储当前最小值
    
public:
    MinStack() {}
    
    void push(int val) {
        dataStack.push(val);
        // 如果minStack为空或新值<=当前最小值
        if (minStack.empty() || val <= minStack.top()) {
            minStack.push(val);
        }
    }
    
    void pop() {
        if (dataStack.empty()) return;
        
        if (dataStack.top() == minStack.top()) {
            minStack.pop();
        }
        dataStack.pop();
    }
    
    int top() {
        return dataStack.top();
    }
    
    int getMin() {
        return minStack.top();
    }
};

设计要点:

  • 使用两个栈分别存储数据和最小值

  • 空间换时间,用O(n)额外空间实现O(1)的getMin操作

  • 注意等于最小值的情况也要入辅助栈

四、综合实战案例

4.1 最大矩形面积(LeetCode 84)

cpp 复制代码
int largestRectangleArea(vector<int>& heights) {
    heights.push_back(0);  // 添加哨兵,确保最后能清空栈
    stack<int> stk;
    int maxArea = 0;
    
    for (int i = 0; i < heights.size(); i++) {
        // 当前高度小于栈顶高度时,计算以栈顶为高的矩形面积
        while (!stk.empty() && heights[stk.top()] > heights[i]) {
            int height = heights[stk.top()];
            stk.pop();
            
            // 计算宽度
            int left = stk.empty() ? -1 : stk.top();
            int width = i - left - 1;
            
            maxArea = max(maxArea, height * width);
        }
        stk.push(i);
    }
    
    heights.pop_back();  // 恢复原数组
    return maxArea;
}

4.2 接雨水(LeetCode 42)(此题更深入的理解可看之前的博客)

cpp 复制代码
int trap(vector<int>& height) {
    stack<int> stk;  // 存储索引
    int water = 0;
    
    for (int i = 0; i < height.size(); i++) {
        while (!stk.empty() && height[i] > height[stk.top()]) {
            int bottom = height[stk.top()];
            stk.pop();
            
            if (stk.empty()) break;
            
            // 计算当前水槽的宽度和高度
            int left = stk.top();
            int width = i - left - 1;
            int h = min(height[i], height[left]) - bottom;
            
            water += width * h;
        }
        stk.push(i);
    }
    
    return water;
}

总结

栈作为基础数据结构,在算法竞赛和实际开发中都有着广泛的应用。掌握栈的关键在于:

  1. 理解LIFO本质:时刻记住后进先出的特性

  2. 识别应用场景:括号匹配、表达式求值、函数调用、单调栈等

  3. 注意边界条件:空栈访问是常见错误源

栈的学习是一个循序渐进的过程,从基础操作到高级应用,再到复杂问题的解决,需要大量的练习和思考。建议从力扣的简单题开始,逐步挑战更复杂的问题,在实践中深化理解。

记住:栈不是万能的,但当发现问题的解决需要"最近相关性"时,栈很可能就是最佳选择!

祝大家在算法学习的道路上不断进步!有任何问题欢迎交流讨论。

相关推荐
We་ct2 小时前
LeetCode 3. 无重复字符的最长子串:滑动窗口最优解演进与解析
前端·算法·leetcode·typescript
weixin199701080162 小时前
B2Bitem_get - 获取商标详情接口对接全攻略:从入门到精通
java·大数据·算法
棱镜Coding2 小时前
LeetCode-Hot100 31.K个一组反转链表
算法·leetcode·链表
Hello World . .2 小时前
数据结构:数据结构基础、顺序表、链表
c语言·数据结构·vim
2401_832131952 小时前
模板编译期机器学习
开发语言·c++·算法
嵌入小生0072 小时前
Data Structure Learning: Starting with C Language Singly Linked List
c语言·开发语言·数据结构·算法·嵌入式软件
ValhallaCoder2 小时前
hot100-子串
数据结构·python·算法
ygklwyf2 小时前
无向图的连通性之割点/边,点/边双连通分量
算法·无向图·圆方树
2401_838472512 小时前
单元测试在C++项目中的实践
开发语言·c++·算法