
◆ 博主名称:Severus-Yuan
◆ 来自泥潭,充满野心,强大冷静,从不后悔
◆ 欢迎光临我的思考天地,期待我们在这刹那的同频共振
| Severus-Yuan的专栏 | 链接🔗 |
|---|---|
| C语言 | C语言 |
| 初阶数据结构与算法 | 初阶数据结构与算法 |
| 数据结构算法题 | 数据结构算法题 |
| C++ | C++ |
文章目录
一.Stack
(1)最小栈

1.题解代码:
cpp
class MinStack {
public:
MinStack() {
//构造函数
}
void push(int value) {
s1.push(value);
if(s2.empty() || value <= s2.top() )
{
s2.push(value);
}
}
void pop() {
int del = s1.top();
s1.pop();
if(s2.top() == del)
{
s2.pop();
}
}
int top() {
return s1.top();
}
int getMin() {
return s2.top();
}
private:
stack<int> s1;
stack<int> s2;
};
2.图文分析:

首先先看清题目要求,使用常数时间解决问题,那么设置min变量遍历找最小值的想法就行不通,不过其实就算允许O(N)的时间复杂度,该思路也不能满足要求,当pop掉当前的最小变量之后,我们又去哪里找上一个最小变量呢?
这里我们采取两个栈的方法。一个栈s1专门用于储存所有元素,而另一个栈s2则是专门用于存储每个阶段的最小值。
push():当s1压入第一个元素时,这个元素一定是当前栈中的最小值,所以也要压入s2中(对应push中的s2.empty()),继续向s1中压入元素时,就要与s2中栈顶的元素进行比较了,只有小于等于的情况,才能压入s2中。这样才能确保每次的s2.top()是s1中的最小值;
pop():当删除s1中的元素时,如果该元素是栈的最小值,那么就要将s2中栈顶对应的最小值一起删除,这样才能保证s2中存储的最小值和s1中的真实情况同步。
MinStack():至于为啥不写MinStack()的构造函数,是因为MinStack的两个成员变量都是自定义类型stack,在MinStack()内部会自动调用stack的默认构造,库里已经实现了,不需要我们多此一举。
(2)栈的压入、弹出序列

1.题解代码:
cpp
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
* @param pushV int整型vector
* @param popV int整型vector
* @return bool布尔型
*/
bool IsPopOrder(vector<int>& pushV, vector<int>& popV) {
// write code here
stack<int> s;
size_t pushVi = 0;
size_t popVi = 0;
while(pushVi<pushV.size())
{
s.push(pushV[pushVi++]);
//相等了
while(!s.empty() && s.top() == popV[popVi])
{
//加上!s.empty(),不然等s为空时还去栈顶元素就会报错
s.pop();
popVi++;
}
}
return s.empty();
}
};
2.图文分析:
感觉这题还挺难的,我看懂解题思路之后写出代码还花了半小时。
乍一看见这道题,你可能会想到很多复杂的思路,这里就不绕弯子了,直接摆出一种比较好理解的思路。
根据题目可知,给出了入栈序列和一个可能的出栈序列,既然这样,那我们就先按着入栈序列将元素一个一个地压入栈中,但在压栈的过程中,如果栈顶的元素(栈中元素只能从栈顶出来)和出栈序列popVi下标处的元素相等(下标popVi从0开始遍历)的话,那就pop掉栈中栈顶的元素,直到栈顶元素和出栈序列popVi下标处的元素不相等了(注意:是判断栈顶元素是否和出栈序列popVi下标处的元素是否相等,不是判断入栈序列pushVi下标处的元素和出栈序列popVi下标处的元素是否相等,这会影响到内层循环的写法。该思路下,元素要先入栈才有比较资格),再继续按着入栈序列将元素压入栈中(定义了下标pushVi,也是从0开始遍历),直到将入栈序列中的所有元素压入栈中。
从这个思路我们也能知道,外层的大循环是"入栈序列中的所有元素压入栈中",而内层嵌套的小循环是"直到栈顶元素和出栈序列popVi下标处的元素不相等了";入栈和出栈序列能匹配上的结果是入栈的元素能够被全部pop掉,也就是在出大循环之后,s为空,不能匹配上,即出大循环之后,s里还有元素。
我录了个视频模拟思路过程,感兴趣地可以看看:栈的压入、弹出序列思路视频模拟。
(3)逆波兰表达式求值

逆波兰表达式也就是后缀表达式,也就是将运算符写在两个操作数的后面。而我们正常写的是中缀表达式,即将运算符写在两个操作数的中间,像3+4,但如果按后缀表达式/逆波兰表达式的写法,就要写成3 4 +。
1.题解代码:
cpp
class Solution {
public:
int evalRPN(vector<string>& tokens) {
stack<int> s;
int ret = 0;
size_t i = 0;
//遍历vector的下标
while(i < tokens.size())
{
if(!(tokens[i]=="+" || tokens[i]=="-" || tokens[i]=="*" || tokens[i]=="/"))
{
s.push(stoi(tokens[i]));
//是数字就入栈
}
else
{
char x = tokens[i][0];
//右操作数
int b = s.top();
s.pop();
//左操作数
int a = s.top();
s.pop();
if(x == '+')
{
ret = a+b;
}else if(x == '-'){
ret = a-b;
}else if(x == '*'){
ret = a*b;
}else{
ret = a/b;
}
s.push(ret);
}
i++;
}
return s.top();
}
};
cpp
class Solution {
public:
int evalRPN(vector<string>& tokens) {
stack<int> s;
int ret = 0;
size_t i = 0;
//处理输入的vector只有一个string元素的情况
if(tokens.size() == 1)
{
return stoi(tokens[0]);
}
while(i < tokens.size())
{
while(!(tokens[i]=="+" || tokens[i]=="-" || tokens[i]=="*" || tokens[i]=="/"))
{
s.push(stoi(tokens[i]));
//是数字就入栈
i++;
}
char x = tokens[i][0];
i++;
//右操作数
int b = s.top();
s.pop();
//左操作数
int a = s.top();
s.pop();
if(x == '+')
{
ret = a+b;
}else if(x == '-'){
ret = a-b;
}else if(x == '*'){
ret = a*b;
}else{
ret = a/b;
}
s.push(ret);
}
return s.top();
}
};
2.分析:

怎么写都行,核心思路就是遇到数字就入栈,遇到运算符就取出刚压入的两个数字作为操作数进行运算,再将所得值压入栈中,成为操作数。直到遍历完vector中的所有string,最后留在栈中的就是运算结果。
运算符的遍历顺序就是运算的优先级顺序。
(4)用栈实现队列

题解代码:
cpp
class MyQueue {
public:
MyQueue() {
//构造函数
}
void push(int x) {
pushst.push(x);
}
int pop() {
if(popst.empty())
{
//popst为空就要先倒值
while(!pushst.empty())
{
popst.push(pushst.top());
pushst.pop();
}
}
int n = popst.top();
popst.pop();
return n;
}
int peek() {
if(popst.empty())
{
//popst为空就要先倒值
while(!pushst.empty())
{
popst.push(pushst.top());
pushst.pop();
}
}
return popst.top();
}
bool empty() {
return pushst.empty() && popst.empty();
}
private:
stack<int> pushst;
stack<int> popst;
};

这道题我曾在学C语言数据结构栈和队列时写过,如果不明思路的,可以去我的另一篇博客【数据结构算法题】用队列实现栈/用栈实现队列(详细解答!)看看,这里就不再赘述了。从这道题也能看出C++有了STL之后,解题就爽多了。
二.Queue
(1)用队列实现栈

题解代码:
cpp
//1.我的实现:
//和【数据结构算法题】用队列实现栈/用栈实现队列(详细解答!)中的思路一致
class MyStack {
public:
MyStack() {
//构造函数
}
void push(int x) {
if(q1.empty())
{
q2.push(x);
}else{
q1.push(x);
}
}
int pop() {
queue<int>* nonqempty = &q1;
queue<int>* qempty = &q2;
if(q1.empty())
{
nonqempty = &q2;
qempty = &q1;
}
size_t oldsize = nonqempty->size();
for(size_t i=0;i<oldsize-1;i++)
{
int tmp = nonqempty->front();
nonqempty->pop();
qempty->push(tmp);
}
int ret = nonqempty->front();
nonqempty->pop();
return ret;
}
int top() {
queue<int>* nonqempty = &q1;
queue<int>* qempty = &q2;
if(q1.empty())
{
nonqempty = &q2;
qempty = &q1;
}
size_t oldsize = nonqempty->size();
for(size_t i=0;i<oldsize-1;i++)
{
int tmp = nonqempty->front();
nonqempty->pop();
qempty->push(tmp);
}
int ret = nonqempty->front();
nonqempty->pop();
qempty->push(ret);
return ret;
}
bool empty() {
return q1.empty() && q2.empty();
}
private:
queue<int> q1;
queue<int> q2;
};
这道题我曾在学C语言数据结构栈和队列时写过,如果不明思路的,可以去我的另一篇博客【数据结构算法题】用队列实现栈/用栈实现队列(详细解答!)看看,这里就不再赘述了。
cpp
//官方提供的一种解法:
class MyStack {
public:
MyStack() {
//构造函数
}
void push(int x) {
//每次都从queue2入队,则每次入队的那一个元素都是模拟栈的栈顶元素
queue2.push(x);
while(!queue1.empty())
{
//queue1中现有的元素也是由每次的栈顶元素累积得来的
queue2.push(queue1.front());
queue1.pop();
}
swap(queue1,queue2);
}
int pop() {
int ret = queue1.front();
queue1.pop();
return ret;
}
int top() {
return queue1.front();
}
bool empty() {
return queue1.empty();
}
private:
queue<int> queue1;
queue<int> queue2;
};

不得不感叹,官方确实牛逼,不仅实现的代码简洁,就连解释也写得精炼而深刻。
(2)二叉树的层序遍历

1.题解代码:
cpp
//我的实现:
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> ret;
if(root == nullptr)
return ret;
//非空树
queue<TreeNode*> q;
//存节点的队列
q.push(root);
while(!q.empty())
{
vector<int> v;
size_t leversize = q.size();
while(leversize--)
{
//完成root的迭代
auto node = q.front();
v.push_back(node->val);
q.pop();
if(node->left)
q.push(node->left);
if(node->right)
q.push(node->right);
}
ret.push_back(v);
}
return ret;
}
};
cpp
//官方提供的解法:
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> ret;
if (!root) {
return ret;
}
//队列装节点而不是节点的值
queue <TreeNode*> q;
q.push(root);
while (!q.empty())
{
int currentLevelSize = q.size();
ret.push_back(vector<int>());
//匿名对象
//放vector<int>的目的是题目要求返回二维数组
for (int i = 1; i <= currentLevelSize; ++i)
{
//最后的两个push加上front间接完成了node的更新
TreeNode* node = q.front();
q.pop();
ret.back().push_back(node->val);
if (node->left)
q.push(node->left);
if (node->right)
q.push(node->right);
}
}
return ret;
}
};
我的实现和官方的实现的思路其实大差不差,最大的区别就是我使用的是vector< int >的有名对象,但是官方使用的是vector< int >的匿名对象。
2.分析:
这题感觉有点难度,一开始的思路有错误后面就容易陷入复杂的局面。
在学习二叉树时,实现二叉树的某个层序遍历的题目时貌似就是借助队列完成的。先将根节点入队,当pop根节点的同时将左右子节点入队,再当pop根节点的左孩子节点的同时,将根节点的左孩子节点的左右子节点入队,根节点的右孩子节点也是同样的逻辑,之后的节点都是这样的逻辑。这样,二叉树中的所有节点能按层序遍历的顺序出现在队列中,具体的题目我忘记了,但这个思路是很重要的。
在这个思路的前提下,我们来看看代码究竟是怎么实现的:
首先先定义一个队列q,用于存树的节点。树的每个节点会按照层序遍历的顺序"流过"该队列,当队列中存储的元素都来自树的同一层时,我们要将此时的所有元素放进v里,再插入到ret里。但是由于队列中既可能出现存储元素都是树的同一层的情况,也可能出现不同层的情况,所以我们还需要定义一个leversize变量,用于记录每层节点的个数(树的每层的节点个数也就是队列的大小)。根据levelsize的大小进行pop且同时插入子节点,就能确保队列中存储的元素都来自于树的同一层。然后再将队列中来自同一层的元素放进v里,每次levelsize消耗完后,就将v插入ret中,成为二维数组的一个元素(听起来像是一次性将队列中来自树的同一层的节点插入v中,但其实是在队列pop元素的同时进行节点数据的插入,因为pop进行levelsize次的这levelsize个节点来自树的同一层)。
------end------