目录
关于栈的题目:
1. 最小栈:
思路:
实现代码(最终):
2. 栈的压入、弹出序列:
思路:
实现代码:
3. 逆波兰表达式求值:
思路:
实现代码:
深入了解逆波兰表达式:
实现代码:
用栈实现队列
思路:
实现代码:
学了栈却不知道该怎么用于做题?本篇将分享几道栈相关的经典题目
关于栈的题目:
1. 最小栈:
思路:
先拿示例一举例,我们可以定义两个栈,st和min
st用来存push进去的数据,而min用来存当前st中数据的最小值,具体怎么实现呢?模拟一下示例1
实现代码:
class MinStack {
public:/** initialize your data structure here. */MinStack() {//这里可以不用写,会调用默认的构造函数}void push(int x) {st.push(x);//先入普通的栈if(min.empty() || x<min.top())//如果这个数据小于等于最小栈中栈顶的数据,就插入,让x成为新的最小值min.push(x);}void pop() {if(st.top() == min.top())//如果要删除的数和最小栈的栈顶的值相同,就代表要删除的这个数也是现在栈中最小的数,所以要把最小栈中的那个数也pop掉min.pop();st.pop();}int top() {return st.top();//取普通栈的栈顶}int getMin() {return min.top();//取最小栈的栈顶}
private:stack<int> st;//普通用来存数据的栈stack<int> min;//用来存每一时期的最小值(栈顶就是当前时期的最小值)
};
但如果运行的话会报错
那我们就用这组出错的数据再模拟一下
所以在第三步时(即插入第二个0)也应将0入栈min
这样即使再getmin,值也还会是0
实现代码(最终):
class MinStack {
public:/** initialize your data structure here. */MinStack() {//这里可以不用写,会调用默认的构造函数}void push(int x) {st.push(x);//先入普通的栈if(min.empty() || x<min.top())//如果这个数据小于等于最小栈中栈顶的数据,就插入,让x成为新的最小值min.push(x);}void pop() {if(st.top() == min.top())//如果要删除的数和最小栈的栈顶的值相同,就代表要删除的这个数也是现在栈中最小的数,所以要把最小栈中的那个数也pop掉min.pop();st.pop();}int top() {return st.top();//取普通栈的栈顶}int getMin() {return min.top();//取最小栈的栈顶}
private:stack<int> st;//普通用来存数据的栈stack<int> min;//用来存每一时期的最小值(栈顶就是当前时期的最小值)
};
2. 栈的压入、弹出序列:
思路:
先定义一个栈,用来模拟入栈出栈
到下一次入栈时,此时st.pop() == pop[i],所以就要开始出栈,直到st.pop() != pop[i]为止
出栈4后,现在的数据是这样
所以要继续入栈
删除完最后一个数据之后
实现代码:
bool IsPopOrder(vector<int>& pushV, vector<int>& popV)
{stack<int> st;//创建一个栈,用来存pushV的数据int popi = 0;//popV的进度for(int i=0;i<pushV.size();i++)//每次都入栈pushV的数据{st.push(pushV[i]);while(!st.empty() && st.top() == popV[popi])//如果现在栈顶的数据等于popV进度的数据,就表示需要开始出栈了{st.pop();popi++;}}return st.empty();//如果最后栈里没有数据了,就代表全pop完了,就是对的
}
3. 逆波兰表达式求值:
思路:
逆波兰表达式,也叫后缀表达式,我们平常用的表达式是中缀表达式,例如a+b,写成后缀表达式就是ab+,即把操作符放到了数的后面,这是为了优先级的区分(更便于计算),优先级越高的操作符就会越在前面
(a+b)*c-(a+b)/e转换成后缀表达式就是ab+c*ab+e/-,即先让"a"和"b"执行"+"操作,然后让"a+b"的值和"c"执行"*"操作,再让"a"和"b"执行"+"操作,以此类推
相信对栈有一定了解的人都已经想到了,这道题可以用栈模拟的方法轻松解决
遇到非操作符就入栈,遇到操作符就把栈的前两个元素出栈并运算,运算完把结果再入栈就可以
具体点来说,拿示例1模拟
示例二模拟:
可以看到,最后存在栈(栈顶)的数据就是最终结果
实现代码:
int evalRPN(vector<string>& tokens)
{stack<int> st;//用一个栈来模拟数运算的先后顺序int i=0;while(i < tokens.size())//遍历数组{if(tokens[i] != "+" && tokens[i] != "-" && tokens[i] != "*" && tokens[i] != "/")//如果不是算数符就入栈st.push(stoi(tokens[i]));else//遇到算数符,就把栈的前两个元素提出来(第一个提出来的是右元素,第二个是左元素){int right = st.top();st.pop();int left = st.top();st.pop();switch(tokens[i][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;}}i++;}return st.top();
}
深入了解逆波兰表达式:
注:面试时一般不会考的这么深入,如果有兴趣可以看看这部分内容
本题中给出的输入就已经是逆波兰表达式了,那中缀表达式是如何转换成逆波兰表达式(后缀表达式)的呢
例如,我们有一个中缀表达式1+2*4+3,根据优先级,它会先算2*4,再拿2*4的结果去算另外两个加式,但如果直接这样计算的话,不避免的会先算加法式,所以要给它转换成后缀表达式后用栈的特性来计算
转换成后缀表达式也需要用栈,下面来模拟一下过程
规则:
- 遇到数字,提出来(尾插到后缀表达式中)
- 遇到操作符,若此时栈为空,就入栈
- 遇到操作符,若此时栈不为空,如果当前操作符比栈顶的操作符优先级要高,就入栈
- 遇到操作符,若此时栈不为空,如果当前操作符比栈顶的操作符优先级要低或相等,就出栈掉相等的操作符(直到栈顶为空或比栈顶的优先级要低),并将出栈的操作符尾插到后缀表达式中,然后再入栈新操作符
实现代码:
#include <iostream>
#include <string>
#include <vector>
#include <stack>
using namespace std;bool op(string s)//判断是否为操作符
{if(s=="+"||s=="-"||s=="*"||s=="/")return true;elsereturn false;
}int priority(string s)//返回该操作符的优先级比
{if(s == "+" || s == "-")return 1;else return 2;
}
int main()
{vector<string> infix = {"1","+","2","*","4","+","3"};//待转换的中缀表达式vector<string> postfix;//待尾插的后缀表达式stack<string> st;//存操作符的栈for(int i=0;i<infix.size();i++){if(op(infix[i]))//如果该位置是操作符{if(st.empty() || priority(st.top()) < priority(infix[i]))//如果栈为空或者栈顶的优先级比当前操作符低,则直接入栈{st.push(infix[i]);}else//否则栈顶的优先级比当前操作符高或相等,则将栈顶的操作符弹出,直到栈顶的优先级比当前操作符低,然后将当前操作符入栈{while(!st.empty() && priority(st.top()) >= priority(infix[i])){string s = st.top();st.pop();postfix.push_back(s);}st.push(infix[i]);}}else//如果该位置是操作数,则直接尾插到后缀表达式中{postfix.push_back(infix[i]);}}while(!st.empty())//将栈中剩余的操作符弹出并尾插到后缀表达式中{string s = st.top();st.pop();postfix.push_back(s);}for(const auto& s : postfix){cout << s << " ";}return 0;
}
输出结果:
用栈实现队列
思路:
众所周知,栈是后进先出(LIFO),而队列是先进先出(FIFO),想用两个栈实现先进先出,可以用一个栈专门入数据,另外一个栈专门出数据
拿这个数据来举例,现在我们要出队的话,会先出1,怎么样才能先读到1呢?
再把数据全部都入到另外一个栈上,此时出栈的顺序就和出队一样了
当然,还有很多细节没有处理
只要要出栈时才会用到popst,所以只有要出栈时,才需要把pushst中的数据都入到popst中
并且,当popst中有数据时,就先不用把pushst中的数据入到popst,因为此时如果再入的话popst栈顶的数据就不是先进的那个数了
实现代码:
class MyQueue {
public:MyQueue() {//让它调用默认的构造函数就行(stack的构造函数)}void push(int x) {//往pushst中插入数据pushst.push(x);}//先在pushst中插入数据,这样再一个个出栈到popst时,再popst.top()就是队尾的数据了int pop() {if(popst.empty())//如果此时popst还是空的,就得先给popst数据{while(!pushst.empty()){int tmp = pushst.top();pushst.pop();popst.push(tmp);}}int x = popst.top();popst.pop();return x;}int peek() {if(popst.empty())//如果此时popst中没有数据,就要先把pushst的数据给popst{while(!pushst.empty()){int tmp = pushst.top();pushst.pop();popst.push(tmp);}}return popst.top();}bool empty() {//只有两个栈里都没有数据才是真没数据return pushst.empty() && popst.empty();}
private:stack<int> pushst;//负责入栈stack<int> popst;//负责出栈
};