代码随想录算法训练营 Day10 | 栈与队列 part02

150. 逆波兰表达式求值

给你一个字符串数组 tokens ,表示一个根据 逆波兰表示法 表示的算术表达式。

请你计算该表达式。返回一个表示表达式值的整数。

注意:

  • 有效的算符为 '+''-''*''/'
  • 每个操作数(运算对象)都可以是一个整数或者另一个表达式。
  • 两个整数之间的除法总是 向零截断 。
  • 表达式中不含除零运算。
  • 输入是一个根据逆波兰表示法表示的算术表达式。
  • 答案及所有中间计算结果可以用 32 位 整数表示。
cpp 复制代码
class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        stack<int>st;
        // 遍历每个 token(数字/运算符)
        for(string token : tokens){
            // isdigit(token[0]):正数;
            // token.size()>1:负数(如"-11")
            if(isdigit(token[0]) || token.size() > 1){
                int num = stoi(token); // 字符串转整数
                st.push(num); // 数字直接入栈
            }
            // 遇到运算符,弹出两个数字进行计算
            else{
                // 注意顺序:先弹出的是右操作数n1,后弹出的是左操作数n2
                int n1 = st.top(); st.pop();
                int n2 = st.top(); st.pop();
                // 根据运算符计算,结果入栈
                if(token == "+") st.push(n2 + n1);
                else if(token == "-") st.push(n2 - n1);
                else if(token == "*") st.push(n2 * n1);
                else if(token == "/") st.push(n2 / n1);
            }
        }
        // 栈中最后剩余的数字就是最终结果
        return st.top();
    }
};

总结

1. 设计思想
  1. 遍历表达式:遇到数字直接入栈;
  2. 遇到运算符:从栈中弹出 2 个数字,先弹的是右操作数,后弹的是左操作数;
  3. 运算后入栈:将计算结果重新压入栈;
  4. 结束:遍历完成后,栈顶元素就是表达式结果。
2. 重点
  1. 运算顺序减法 / 除法不满足交换律,必须用后弹出的 n2 去运算先弹出的 n1,正确:n2-n1
  2. 负数判断负数首字符是 -isdigit() 会判定为非数字,必须增加 长度>1 的判断条件;
3. 复杂度分析
  • 时间复杂度:O(n) 仅遍历一次表达式数组,每个数字入栈 / 出栈各一次;
  • 空间复杂度:O(n) 栈存储表达式中的所有数字,最坏情况全为数字。

239. 滑动窗口最大值

给你一个整数数组 nums,有一个大小为 k的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。

返回 滑动窗口中的最大值

cpp 复制代码
class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        deque<int> que;  // 双端队列:存储元素下标,维护队列对应数值【单调递减】
        vector<int> ans; // 存储每个窗口的最大值
        for(int i = 0; i < nums.size(); i++){
            // 1. 维护单调递减:当前元素 > 队尾元素,弹出队尾(保证队头永远是最大值)
            while(!que.empty() && nums[i] > nums[que.back()]) {
                que.pop_back();
            }
            que.push_back(i); // 当前元素下标入队
            // 2. 移除窗口外的元素:队头下标超出窗口左边界,弹出队头
            if(i - que.front() + 1 > k) {
                que.pop_front();
            }
            // 3. 窗口形成(i >= k-1),记录队头(窗口最大值)
            if(i >= k-1) {
                ans.push_back(nums[que.front()]);
            }
        }
        return ans;
    }
};

总结

1. 设计思想
  1. 队列存储下标:方便判断元素是否在窗口内;
  2. 单调递减维护:队列中元素对应的值从队头到队尾严格递减,队头永远是当前窗口最大值;
  3. 窗口边界控制:实时移除超出窗口范围的队头元素;
  4. 窗口成型后:收集队头元素作为当前窗口最大值。
2. 重点
  1. 单调队列维护必须保证队列单调递减,才能让队头始终是最大值;
  2. 窗口边界i - que.front() + 1 > k 精准移除窗口外的过期元素;
  3. 结果收集时机只有当 i >= k-1 时,窗口才第一次成型,开始记录最大值。
3. 复杂度分析
  • 时间复杂度:O(n) 每个元素仅入队、出队一次,线性遍历,完美解决大数据超时;
  • 空间复杂度:O(k) 双端队列最多存储窗口内的 k 个元素。

347. 前 K 个高频元素

给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。

cpp 复制代码
class Solution {
public:
    // 自定义比较器:实现小顶堆(升序排列,堆顶为频率最小的元素)
    class cmp{
    public:
        bool operator() (const pair<int,int>& a, const pair<int,int>& b){
            return a.second > b.second;
        }
    };
    vector<int> topKFrequent(vector<int>& nums, int k) {
        unordered_map<int,int> mp; // 哈希表:key=数字,value=出现频率
        vector<int> ans;
        // 1. 统计每个数字出现的频率
        for(int i=0;i<nums.size();i++){
            mp[nums[i]]++;
        }
        // 2. 定义小顶堆:存储<数字,频率>,按频率升序
        priority_queue<pair<int,int>,vector<pair<int,int>>,cmp> que;
        // 3. 遍历哈希表,维护大小为k的小顶堆
        for(auto i : mp){
            que.push(i);
            // 堆大小超过k,弹出堆顶(频率最小的元素)
            if(que.size() > k) que.pop();
        }
        // 4. 堆中剩余的k个元素就是TopK高频元素,收集结果
        while(!que.empty()){
            ans.push_back(que.top().first);
            que.pop();
        }
        return ans;
    }
};

总结

1. 设计思想
  1. 哈希表统计:快速记录每个数字的出现次数;
  2. 小顶堆筛选:
    • 堆的大小固定为 k
    • 遍历所有频率,若堆超容则弹出频率最小的元素;
    • 最终堆中保留的就是频率最高的 k 个元素;
  3. 结果收集:将堆中元素取出即为答案。
2. 重点
  1. 小顶堆比较器return a.second > b.second 是小顶堆的关键,大于号决定升序排列;
  2. 堆的大小控制始终保持堆大小为 k,保证最优时空效率;
  3. 存储结构用 pair<数字, 频率> 存储,方便同时关联数值与频率。
3. 复杂度分析
  • 时间复杂度:O(nlogk) n 为数组长度,logk 为堆的调整开销,远优于排序的 O(nlogn);
  • 空间复杂度:O(n) 哈希表存储所有元素,堆仅占用 O(k) 空间。

栈与队列章节总结

一、题型

1. 基础互模拟题(栈 ↔ 队列)
  • 用栈实现队列:双栈(输入栈 + 输出栈),利用栈反转元素顺序,实现 FIFO
  • 用队列实现栈:单队列循环移动元素,实现 LIFO
2. 栈的经典应用(核心:先进后出)
  • 有效的括号:栈匹配嵌套括号,左括号压入对应右括号,一键匹配
  • 删除相邻重复项:栈 / 字符串模拟栈,消除相邻重复元素
  • 逆波兰表达式求值:栈存数字,遇运算符弹出计算,固定运算顺序
3. 高阶数据结构
  • 滑动窗口最大值:单调递减双端队列,线性时间维护窗口最值
  • 前 K 个高频元素:哈希表统计频率 + 小顶堆筛选 TopK

二、核心

  1. 栈:处理相邻匹配、嵌套、后缀表达式的首选结构
  2. 字符串模拟栈:替代栈容器,代码更简洁(删除重复项)
  3. 单调队列:滑动窗口求最值,时间复杂度压至 O(n)
  4. 小顶堆:TopK 问题最优解,空间仅保留 k 个元素

三、重难点

  1. 核心难点:单调队列(滑动窗口)、堆的自定义排序(TopK)
  2. 易错点:逆波兰表达式运算顺序、栈 / 队列模拟的边界判断
  3. 最优原则:能用模拟就不用冗余空间,能用线性复杂度就不用暴力解法
相关推荐
Darkwanderor2 小时前
数据结构——树状数组和在线、离线操作
数据结构·c++·树状数组·离线操作
仟濹3 小时前
【算法打卡day22(2026-03-14 周六)今日算法or技巧:双指针 & 链表】9个题
数据结构·算法·链表·双指针
OKkankan4 小时前
红黑树的原理及实现
开发语言·数据结构·c++·算法
Eward-an4 小时前
【详细解析】删除有序数组中的重复项 II
数据结构·算法
Book思议-4 小时前
线性表之顺序表入门:顺序表从原理到实现「增删改查」
数据结构·算法
I_LPL4 小时前
day52 代码随想录算法训练营 图论专题6
java·数据结构·算法·图论
小鸡吃米…4 小时前
Python线程同步
开发语言·数据结构·python
XW01059995 小时前
5-6统计工龄
数据结构·python·算法
样例过了就是过了5 小时前
LeetCode热题100 电话号码的字母组合
数据结构·c++·算法·leetcode·dfs