代码随想录:栈和队列篇

232. 用栈实现队列

  1. 核心思路 :用两个栈(输入栈stIn、输出栈stOut)模拟队列(先进先出),输入栈负责存数据,输出栈负责取数据,仅当输出栈为空时,才将输入栈所有数据倒腾到输出栈(实现顺序反转);
  2. 关键操作
    • push:直接压入输入栈,O(1);
    • pop/peek:输出栈空则倒腾输入栈数据,再从输出栈取栈顶(peek需把取出的元素回压),均摊O(1);
    • empty:输入栈+输出栈都为空则队列空;
  3. 复杂度:时间均摊O(1),空间O(n)(存储n个元素)。
C++代码
cpp 复制代码
class MyQueue {
public:
    stack<int> stIn;  // 输入栈:存push的元素
    stack<int> stOut; // 输出栈:供pop/peek取元素

    MyQueue() {}

    // 入队:直接压入输入栈
    void push(int x) {
        stIn.push(x);
    }

    // 出队:输出栈空则倒腾输入栈,再弹输出栈顶
    int pop() {
        if (stOut.empty()) {
            while (!stIn.empty()) {
                stOut.push(stIn.top());
                stIn.pop();
            }
        }
        int res = stOut.top();
        stOut.pop();
        return res;
    }

    // 查队首:复用pop,再把元素回压
    int peek() {
        int res = this->pop();
        stOut.push(res);
        return res;
    }

    // 判空:两个栈都空则队空
    bool empty() {
        return stIn.empty() && stOut.empty();
    }
};

关键点回顾

  1. 两个栈分工:输入栈存、输出栈取,倒腾数据仅在输出栈空时执行,避免重复操作;
  2. 复用逻辑:peek直接调用pop后回压元素,减少重复代码(工业开发核心习惯);
  3. 均摊复杂度:每个元素仅被倒腾一次,pop/peek整体仍为O(1)。

225. 用队列实现栈

  1. 核心思路:利用队列"先进先出"特性模拟栈"先进后出",核心是将队列前n-1个元素移到队尾,让最后入队的元素(栈顶)出现在队首,支持pop/top操作;
  2. 实现方式
    • 双队列:que1存数据,que2做备份(弹出时暂存前n-1个元素);
    • 单队列(优化版):无需备份队列,直接将前n-1个元素移到当前队列尾部,更简洁;
  3. 复杂度:push/empty为O(1),pop/top为O(n)(需移动n-1个元素),空间O(n)。
C++代码(单队列优化版,推荐)
cpp 复制代码
class MyStack {
public:
    queue<int> que; // 仅用一个队列模拟栈

    MyStack() {}

    // 入栈:直接入队(队尾=栈顶)
    void push(int x) {
        que.push(x);
    }

    // 出栈:将前n-1个元素移到队尾,弹出队首(原队尾=栈顶)
    int pop() {
        int size = que.size() - 1;
        while (size--) {
            que.push(que.front()); // 队首移到队尾
            que.pop();
        }
        int res = que.front();
        que.pop();
        return res;
    }

    // 查栈顶:复用pop逻辑,获取后将元素移回队尾(不删除)
    int top() {
        int size = que.size() - 1;
        while (size--) {
            que.push(que.front());
            que.pop();
        }
        int res = que.front();
        que.push(que.front()); // 栈顶元素移回队尾,保持队列结构
        que.pop();
        return res;
    }

    // 判空:队列空则栈空
    bool empty() {
        return que.empty();
    }
};

关键点回顾

  1. 单队列核心操作:弹出/查栈顶时,将队列前n-1个元素"循环移到队尾",让栈顶元素出现在队首;
  2. 双队列思路:que1存数据,que2暂存前n-1个元素,弹出后再导回que1(逻辑等价但代码稍繁琐);
  3. top与pop逻辑复用:top仅多一步"将栈顶元素移回队尾",避免重复代码。

20. 有效的括号

  1. 核心思路:利用栈的"后进先出"特性匹配括号,左括号入栈对应右括号,遇到右括号时校验是否与栈顶匹配,覆盖3类不匹配场景(左括号多余、类型不匹配、右括号多余);
  2. 关键技巧:匹配左括号时直接入栈对应右括号,简化后续匹配逻辑(只需比较栈顶与当前右括号是否相等);
  3. 复杂度:时间O(n)(遍历字符串),空间O(n)(栈存储左括号对应右括号)。
C++代码
cpp 复制代码
class Solution {
public:
    bool isValid(string s) {
        if (s.size() % 2 != 0) return false; // 奇数长度直接不合法
        stack<char> st;
        for (char c : s) {
            // 左括号入栈对应右括号
            if (c == '(') st.push(')');
            else if (c == '{') st.push('}');
            else if (c == '[') st.push(']');
            // 右括号无匹配/类型不匹配
            else if (st.empty() || st.top() != c) return false;
            // 匹配成功,弹出栈顶
            else st.pop();
        }
        // 栈空则全匹配,否则左括号多余
        return st.empty();
    }
};

关键点回顾

  1. 前置校验:字符串长度为奇数时直接返回false(括号无法成对);
  2. 匹配逻辑:左括号存对应右括号,右括号校验栈顶,不匹配/栈空直接返回false;
  3. 最终校验:遍历完字符串后栈为空,说明所有括号都成对匹配。

1047. 删除字符串中的所有相邻重复项

  1. 核心思路:用"栈"的思想匹配相邻重复项------遍历字符时,若当前字符与栈顶(结果串末尾)相同则删除栈顶,否则入栈,最终栈内剩余字符即为无相邻重复的结果;
  2. 优化技巧 :直接用字符串模拟栈(省去栈转字符串+反转的步骤),result.back()取栈顶、pop_back()删栈顶、push_back()入栈,效率更高;
  3. 复杂度:时间O(n)(遍历一次字符串),空间O(1)(返回值不计入,仅用结果串存储)。
核心代码(字符串模拟栈,最优版)
cpp 复制代码
class Solution {
public:
    string removeDuplicates(string S) {
        string res; // 用字符串直接模拟栈
        for (char c : S) {
            // 栈非空且当前字符与栈顶相同 → 删栈顶;否则入栈
            if (!res.empty() && res.back() == c) {
                res.pop_back();
            } else {
                res.push_back(c);
            }
        }
        return res;
    }
};

关键点回顾

  1. 栈的核心作用:记录已遍历的字符,快速校验当前字符是否与前一个重复;
  2. 字符串模拟栈优势:无需额外栈空间,也不用反转结果(栈实现需反转);
  3. 逻辑本质:相邻重复项"成对消除",消除后新的相邻项继续校验,直到无重复。

150. 逆波兰表达式求值

  1. 核心思路:利用栈的"后进先出"特性,遇到数字入栈,遇到运算符弹出栈顶两个数字(注意顺序:后弹出的是左操作数),计算后将结果入栈,最终栈顶即为表达式结果;
  2. 关键细节
    • 运算符计算顺序:num2(先弹) op num1(后弹)(如减法是num2 - num1,除法是num2 / num1);
    • long long避免整数溢出(力扣测试数据需兼容大数);
  3. 复杂度:时间O(n)(遍历所有token),空间O(n)(栈存储数字)。
C++代码
cpp 复制代码
class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        stack<long long> st; // 用long long防止溢出
        for (auto& token : tokens) {
            // 遇到运算符,弹出两个数计算
            if (token == "+" || token == "-" || token == "*" || token == "/") {
                long long num1 = st.top(); st.pop();
                long long num2 = st.top(); st.pop();
                if (token == "+") st.push(num2 + num1);
                else if (token == "-") st.push(num2 - num1);
                else if (token == "*") st.push(num2 * num1);
                else st.push(num2 / num1); // 题目保证除数非0,除法取整
            } else {
                // 数字转long long入栈
                st.push(stoll(token));
            }
        }
        return st.top(); // 最终栈顶为结果
    }
};

关键点回顾

  1. 运算顺序:栈顶第一个弹出的是右操作数(num1),第二个弹出的是左操作数(num2),避免num1 - num2/num1 / num2的顺序错误;
  2. 类型选择:stoll将字符串转long long,栈存储long long,防止大数运算溢出;
  3. 简化逻辑:无需额外弹出栈顶结果,直接返回st.top()(题目保证表达式有效,栈最终仅一个元素)。

239. 滑动窗口最大值

  1. 核心思路 :用单调递减队列维护窗口内"可能的最大值",队列仅保留比当前元素大的数,保证队首始终是窗口最大值;
  2. 队列操作规则
    • pop(value):仅当窗口移除的元素是队首最大值时,才弹出队首;
    • push(value):将队列尾部所有小于当前值的元素弹出,再入队(保持单调递减);
  3. 复杂度:时间O(n)(每个元素仅入队/出队一次),空间O(k)(队列最多存k个元素)。
C++代码
cpp 复制代码
class Solution {
private:
    // 单调递减队列(队首=窗口最大值)
    class MonotonicQueue {
    public:
        deque<int> que;
        // 弹出窗口移除的元素(仅当该元素是队首最大值时)
        void pop(int val) {
            if (!que.empty() && val == que.front()) que.pop_front();
        }
        // 入队:弹出尾部所有更小的元素,保持单调递减
        void push(int val) {
            while (!que.empty() && val > que.back()) que.pop_back();
            que.push_back(val);
        }
        // 获取当前窗口最大值(队首)
        int front() { return que.front(); }
    };

public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        MonotonicQueue mq;
        vector<int> res;
        // 初始化前k个元素
        for (int i = 0; i < k; i++) mq.push(nums[i]);
        res.push_back(mq.front());
        // 滑动窗口遍历剩余元素
        for (int i = k; i < nums.size(); i++) {
            mq.pop(nums[i - k]); // 移除窗口左侧元素
            mq.push(nums[i]);    // 加入窗口右侧新元素
            res.push_back(mq.front()); // 记录当前窗口最大值
        }
        return res;
    }
};

关键点回顾

  1. 单调队列核心:不维护窗口所有元素,仅保留"可能成为后续窗口最大值"的元素,避免暴力遍历;
  2. 操作精简:pop仅处理队首最大值,push通过弹出更小元素保证单调性,二者结合让队首始终是窗口最大值;
  3. 线性复杂度:每个元素仅入队/出队一次,整体时间O(n),远超暴力O(n×k)的效率。

347. 前 K 个高频元素

  1. 核心思路 :先统计元素频率(哈希表),再用大小为k的小顶堆筛选前k个高频元素(小顶堆每次弹出最小频率元素,最终堆内留存前k大);
  2. 关键细节
    • 小顶堆比较规则:频率大的元素"下沉",保证堆顶是当前堆中频率最小的;
    • 堆大小超过k时弹出堆顶,仅维护k个元素,时间复杂度优化至O(nlogk)(优于O(nlogn));
  3. 复杂度:时间O(nlogk),空间O(n)(哈希表+堆)。
C++代码
cpp 复制代码
class Solution {
public:
    // 小顶堆比较规则:频率大的元素优先级低(保证堆顶是最小频率)
    struct Cmp {
        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) {
        // 1. 统计元素频率
        unordered_map<int, int> freq;
        for (int num : nums) freq[num]++;

        // 2. 小顶堆筛选前k个高频元素
        priority_queue<pair<int, int>, vector<pair<int, int>>, Cmp> heap;
        for (auto& p : freq) {
            heap.push(p);
            if (heap.size() > k) heap.pop(); // 堆超k则弹出最小频率元素
        }

        // 3. 倒序收集结果(小顶堆堆顶是第k大,需从后往前填)
        vector<int> res(k);
        for (int i = k-1; i >= 0; i--) {
            res[i] = heap.top().first;
            heap.pop();
        }
        return res;
    }
};

关键点回顾

  1. 小顶堆的优势:仅维护k个元素,每次插入/弹出的时间复杂度是O(logk),整体比全排序(O(nlogn))更高效;
  2. 比较规则:a.second > b.second 是小顶堆的核心(与常规排序相反),保证堆顶始终是当前堆中频率最小的元素;
  3. 结果收集:小顶堆弹出顺序是"频率从小到大",因此需倒序存入结果数组。
相关推荐
2401_858286111 小时前
OS54.【Linux】System V 共享内存(3) “共享内存+管道“修bug记录
linux·运维·服务器·算法·bug
小付同学呀1 小时前
C语言学习(二)——C语言数据类型
数据结构·算法
之歆1 小时前
Varnish HTTP 缓存服务器完全指南
服务器·http·缓存
流云鹤1 小时前
牛客周赛Round 131
算法
重生之后端学习1 小时前
124. 二叉树中的最大路径和
java·数据结构·算法·职场和发展·深度优先·图论
mit6.8241 小时前
状压+dijk |floyd
算法
Renhao-Wan1 小时前
Java 算法实践(五):二叉树遍历与常见算法题
java·数据结构·算法
知识即是力量ol2 小时前
口语八股——计算机网络篇(终篇)
java·计算机网络·面试·八股
一条大祥脚2 小时前
Z函数/拓展KMP
算法