Stack & Queue

Stack & Queue

1、最小栈

最小栈

这道题有一个要求:在常数时间内检索到最小元素。意味着我们不能遍历栈。

怎么办呢?

有人想到:封装一个栈,和一个整型min。每次入栈时比较并判断是否更新min。

但是这里有一个问题:如果出栈时,出的数据是最小值,由于min没有办法记录第二小的数,这种方法就会出错。

怎么办呢?

我们不妨封装两个栈:st、min_st。

第一次,两个栈都入数据:

st入数据。

当st入的数据,小于等于min_st的栈顶时,min_st也入数据:

当st入的数据,大于min_st的栈顶时,min_st就不入数据:

st出数据。

当st出的数据,大于min_st的栈顶时,min_st不出数据:

当st出的数据,等于min_st的栈顶时,min_st就出数据:

最后返回最小值,只需返回min_st的栈顶即可。

这里有两个细节:

  1. 由于一开始,两个栈中没有元素,所以第一个数据必须入两个栈。
  2. 入数据时,等于栈顶的数据也要入min_st。(画图理解)

代码演示:

cpp 复制代码
class MinStack {
public:
    MinStack() {
        // 初始化列表没有,声明处也没有,就调用默认构造
    }
    
    void push(int val) {
        _st.push(val);
        if (_min_st.empty() || val <= _min_st.top()) _min_st.push(val);
        // 假如_min_st的数都出完了,st还留着大的数,此时_min_st为空而_st不为空
        // 所以判断_min_st是否入数据的依据,不是_st.empty(),而是_min_st.empty()
    }
    
    void pop() {
        if (_st.top() <= _min_st.top()) _min_st.pop();
        _st.pop();
    }
    
    int top() {
        return _st.top();
    }
    
    int getMin() {
        return _min_st.top();
    }
private:
    stack<int> _st;
    stack<int> _min_st;
};

2、验证栈顺序

验证栈顺序

这里我们的思路是:定义一个栈,用来模拟栈的压入、弹出顺序。

对于例一:

不断压入pushed序列。

当栈顶和popped序列指针位置的值相等:

我们就可以把4出栈,然后popped序列指针位置向后:

当有多个栈顶和popped序列指针位置的值相等情况出现时,那就出栈:


直到pushed的指针遍历完。

此时栈为空,结果也说明了当前弹出序列合法。

对于例二:

我们重复对于例一的操作:

我们发现,此时栈并不是空的,而结果说明了当前弹出序列不合法。

代码演示:

cpp 复制代码
class Solution {
public:
    bool validateStackSequences(vector<int>& pushed, vector<int>& popped) {
        int n = pushed.size();
        int ptr_push = 0, ptr_pop = 0;
        while (ptr_push < n)
        {
            _st.push(pushed[ptr_push]);// 序列中的每一个值都要入栈
            while (!_st.empty() && _st.top() == popped[ptr_pop])
            {// 不为空,且相等
                _st.pop();
                ++ptr_pop;
            }
            ++ptr_push;
        }
        return _st.empty();
    }
private:
    stack<int> _st;
};

3、逆波兰表达式求值

逆波兰表达式求值

简单理解一下逆波兰表示法

我们之前见过的表达式,都是中缀表达式:
1 + 2 1+2 1+2

而后缀表达式,将操作数全部放到了左侧,操作符放到了右侧:
12 + 12+ 12+

再举一个例子:
1 + 2 ∗ 3 − 4 1+2*3-4 1+2∗3−4

转化成后缀表达式:
123 ∗ + 4 − 123*+4- 123∗+4−

我们可以使用括号,凸显出操作符的优先级:
((1(23*)+)4-)

这就是逆波兰表示法。

我们不难发现,逆波兰表示法的表达式,数字比较集中在左侧。所以我们就可以使用Stack数据结构:

  1. 当遇到数字,入栈
  2. 当遇到操作符,连续出两次栈,将得到的两个数与操作符组合,计算结果再入栈

最后返回栈顶(最终结果)即可。

代码演示:

cpp 复制代码
class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        for (auto& e : tokens)
        {
            // 遇到数字
            if (e != "+" && e != "-" && e != "*" && e != "/") _st.push(stoi(e));
            else// 遇到操作符
            {
                // 找出左右操作数
                int right = _st.top();
                _st.pop();
                int left = _st.top();
                _st.pop();
                switch (e[0])// 算出结果,入栈
                {
                    case '+':
                        _st.push(left+right);
                        break;
                    case '-':
                        _st.push(left-right);
                        break;
                    case '*':
                        _st.push(left*right);
                        break;
                    case '/':
                        _st.push(left/right);
                        break;
                }
            } 
        }
        return _st.top();
    }
private:
    stack<int> _st;
};

4、二叉树的层序遍历

二叉树的层序遍历

这道题,除了要层序遍历,还需要我们返回一个二维数组,二维数组记录二叉树对应位置节点存储的数据:

我们先实现出二叉树的层序遍历:

cpp 复制代码
class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        if (root)// 入根节点
            _q.push(root);

        while (!_q.empty())
        {
            TreeNode* front = _q.front();
            _q.pop();
            if (front->left)// 入左孩子
                _q.push(front->left);
            if (front->right) // 入有孩子
                _q.push(front->right);
        }
    }
private:
    queue<TreeNode*> _q;
};

要完成题目要求,我们面临的问题是:如何控制层序遍历是每层每层进行的,以保证我们能得到每层的数据?

我们不妨定义一个记录每层节点个数的整型值:levelSize。

当入完第一层节点,第一层节点个数就是当前队列的size():

出完第一层节点,入完第二层节点,第二层的节点个数也是当前队列的size():

出完第二层节点,入完第三层节点,第三层的节点个数也是当前队列的size():

出一层,入一层节点的全过程,我们就可以用while (leveSize--)控制。

代码演示:

cpp 复制代码
class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        if (root)// 入根节点
            _q.push(root), levelSize = 1;

        while (!_q.empty())
        {
            while (levelSize--)
            {
                TreeNode* front = _q.front();
                tmp.push_back(front->val);
                _q.pop();
                if (front->left)// 入左孩子
                    _q.push(front->left);
                if (front->right) // 入有孩子
                    _q.push(front->right);
            }
            levelSize = _q.size();
            ret.push_back(tmp);
            tmp.clear();
        }
        return ret;
    }
private:
    queue<TreeNode*> _q;
    size_t levelSize;
    vector<vector<int>> ret;
    vector<int> tmp;
};
相关推荐
折哥的程序人生 · 物流技术专研25 分钟前
Java面试85题图解版 · 特别篇:2026后端高频面试题复盘(算法底层逻辑+高并发架构设计全解析,附Java实战代码)
java·网络·数据库·算法·面试
玖玥拾1 小时前
C/C++ 基础笔记(十四)多态与模板编程
c语言·c++·多态·模板
想吃火锅10052 小时前
【leetcode】14.最长公共前缀js
算法·leetcode·职场和发展
Roann_seo%2 小时前
C++文件操作完全指南:从文本读写到二进制文件处理
开发语言·c++
坚果派·白晓明3 小时前
【鸿蒙PC】SDL3 适配:AtomCode + Skills 快速集成 NAPI 测试工具
c++·华为·ai编程·harmonyos·atomcode
云絮.3 小时前
数据库操作
数据库·mysql·算法·oracle
小林ixn3 小时前
LeetCode 206. 反转链表(迭代 + 递归详解)
算法·leetcode·链表
凡人叶枫3 小时前
Effective C++ 条款17:以独立语句将 newed 对象置入智能指针
java·linux·开发语言·c++·算法
凡人叶枫5 小时前
Effective C++ 条款16:成对使用 new 和 delete 时要采取相同形式
开发语言·c++·effective c++
菜鸟‍5 小时前
LeetCode 1 27 和 704 || 两数之和 移除元素 二分查找
算法·leetcode·职场和发展