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的栈顶即可。
这里有两个细节:
- 由于一开始,两个栈中没有元素,所以第一个数据必须入两个栈。
- 入数据时,等于栈顶的数据也要入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数据结构:
- 当遇到数字,入栈
- 当遇到操作符,连续出两次栈,将得到的两个数与操作符组合,计算结果再入栈
最后返回栈顶(最终结果)即可。
代码演示:
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;
};