文章目录
232.用栈实现队列
整体思路
此题比较简单,栈是先进后出,队列是先进先出。想要用栈来实现队列,明显就是直接用两个栈,来实现数据的出栈顺序。
把栈A的数据出栈,压入到栈B中。然后数据再从栈B出栈。就用栈实现了先进先出。
int pop()
- 首先一定要保证stackOut为空,不然的话顺序就乱掉了
int peek()
- 直接使用已有的pop函数,直接复用pop(), 要不然,对stOut判空的逻辑又要重写一遍。
“再多说一些代码开发上的习惯问题,在工业级别代码开发中,最忌讳的就是 实现一个类似的函数,直接把代码粘过来改一改就完事了。”
- 如果使用pop函数弹出了元素res,一定要添加再添加回去
伪代码
stackIn stackOut;
//入栈
void push (int x){
stackIn.push(x);
}
//出栈
int pop()
{
if (stackOut.empty())
while (!stackIn.empty())
{ stackOut.push(stackIn.top);
stackIn.pop();
}
result = stackOut.top();
stackOut.pop;
return result
}
//获取队列出口处的第一个元素,这个我们还是要从out栈里边获取这个元素
int peek()
{
result = this->pop();
stackOut.push(result);//为什么我们要pop出来然后又push进去,因为我们就只是查询一下而已
return result;
}
C++ 代码
class MyQueue {
public:
stack<int> stIn;
stack<int> stOut;
/** Initialize your data structure here. */
MyQueue() {
}
/** Push element x to the back of queue. */
void push(int x) {
stIn.push(x);
}
/** Removes the element from in front of queue and returns that element. */
int pop() {
// 只有当stOut为空的时候,再从stIn里导入数据(导入stIn全部数据)
if (stOut.empty()) {
// 从stIn导入数据直到stIn为空
while(!stIn.empty()) {
stOut.push(stIn.top());
stIn.pop();
}
}
int result = stOut.top();
stOut.pop();
return result;
}
/** Get the front element. */
int peek() {
int res = this->pop(); // 直接使用已有的pop函数
stOut.push(res); // 因为pop函数弹出了元素res,所以再添加回去
return res;
}
/** Returns whether the queue is empty. */
bool empty() {
return stIn.empty() && stOut.empty();
}
};
225. 用队列实现栈
用一个队列来模拟栈的进元素和出元素
整体思路
设队列A,队列从出口到入口的顺序[1, 2, 3]。
栈B,从栈底到栈顶是[1, 2, 3]
关键在于pop到时候,栈要先弹出元素3,那么现在我们只有一个队列,如何先弹出3呢?
其实我们只需要把元素1取出来加入队列的队尾,把元素2取出来加入队列的队尾,这样元素3就到队列的出口了,我们就可以先弹出元素3。更加一般得来说,如果队列长度是size,我们把size - 1个元素弹出即可。
伪代码
que
void push (int x)
{
que.push(x);
}
void pop ()
{
//获取队列的size,因为上文介绍过了,我们需要弹出size - 1个元素
size = que.size();
size--; //此时就是size-1了
while(size--)
{
que.push(que.front()) //就是把我们的前部元素直接加回到队列里面front只是取了第一个元素,没有做弹出
que.pop(); //这里就把前部元素弹出了
}
result = que.front();
que.pop;
return result;
}
//获取栈出口处的第一个元素,而不用弹出
void top()
{
return que.back()
}
C++代码
class MyStack {
public:
queue<int> que1;
queue<int> que2; // 辅助队列,用来备份
/** Initialize your data structure here. */
MyStack() {
}
/** Push element x onto stack. */
void push(int x) {
que1.push(x);
}
/** Removes the element on top of the stack and returns that element. */
int pop() {
int size = que1.size();
size--;
while (size--) { // 将que1 导入que2,但要留下最后一个元素
que2.push(que1.front());
que1.pop();
}
int result = que1.front(); // 留下的最后一个元素就是要返回的值
que1.pop();
que1 = que2; // 再将que2赋值给que1
while (!que2.empty()) { // 清空que2
que2.pop();
}
return result;
}
/** Get the top element. */
int top() {
return que1.back();
}
/** Returns whether the stack is empty. */
bool empty() {
return que1.empty();
}
};
20. 有效的括号
文字链接:20. 有效的括号
题目分析
由于栈结构的特殊性,非常适合做对称匹配类的题目
首先我们需要理解三种括号不匹配的情况
-
第一种情况,字符串里左方向的括号多余,所以不匹配
( [ { } ] ( ),其中最左边多余一个 (
-
第二种情况,括号没有多余,但是括号的类型没有匹配上
( [ { } } },其中方括号 [ 匹配错误,圆括号 ( 也匹配错误
- 第三种情况,字符串里右方向的括号多余了,所以不匹配
( [ { } ] ) ) ),其中右方向的括号多余,不匹配
那么次题中,栈用来干什么呢?
我们从第一个符号开始遍历,每遍历一次,我们把它的右括号入栈,然后等到匹配的时候,对应出栈即可。非常简单方便。
综上:
第一种情况:已经遍历完了字符串,但是栈不为空,说明有相应的左括号没有右括号来匹配,所以return false
第二种情况:遍历字符串匹配的过程中,发现栈里没有要匹配的字符。所以return false
第三种情况:遍历字符串匹配的过程中,栈已经为空了,没有匹配的字符了,说明右括号没有找到对应的左括号return false
字符串遍历完之后,栈是空的,就说明全都匹配了。
再强调一个重要的点就是,如果我们遇到左括号,应该把右括号入栈,到时候出栈的时候有利于我们的代码实现(这样很方便匹配)。等到只要有右括号,我们直接拿这个右括号和我们的栈顶匹配,匹配上了,把该右括号出栈
剪枝
如果是找匹配的括号,那么这个字符串肯定是个偶数。
伪代码
stack<char> st;
if (s.size % 2 != 0) return false;
for (i = 0; i < s.size; i++)
{
//遇到左括号的场景
if (s[1] == '(') st.push(')');
else if (s[i] == '{') st.push('}');
else if (s[i] == '[') st.push(']');
//遇到右括号的场景,此时我们要到栈内去匹配
//第三种情况,匹配的时候栈已经空了,没有匹配的字符了st.empty()
//第二种情况,遍历字符串匹配的过程中,发现栈里不是我们要匹配的字符。st.top()!=s[i]
//一定要注意,先检查空栈的情况,再去看匹配不上的情况,不能写成st.top() != s[i]||st.empty()
else if (st.empty() || st.top() != s[i]) return false;
else st.pop(); //st.top() 与 s[i]相等,栈弹出元素
}
return st.empty();
1047. 删除字符串中的所有相邻重复项
文字链接:1047. 删除字符串中的所有相邻重复项
次题如果不没想到要用栈和队列的思想。过程就相当复杂
不仅要找相邻的重复项,还要在相邻的重复项删除了之后如果又有相邻处,还要继续删除。
比如对于字符串"abbaca"删除重复项之后变成"aaca"还要再次删除"aa"。这样就相当麻烦。
- 所以次题为什么适合用栈来解决呢?
两个一样的字符相邻就删除,像一种消消乐的规则,这类问题用栈也非常合适,其实也像一个匹配题
- 那么次题中,栈用来干什么呢?
用来存我们遍历过的元素,我们每遍历一个元素,都去栈里面询问一下,我之前有没有遍历a,我的前一个字母是a吗。其实就是通过栈这个数据结构来获得我们前一个遍历的元素是什么。
如果前后元素不一样,我们就继续往后遍历,并入栈,直到碰到相邻重复元素。
例如对于字符串“abbac”。我们现在遍历到第二个b,然后栈顶元素也是b,那么就消除,一下个遍历a,但是由于栈顶的b被消除了,此时的栈顶是a,然后我们再次完成消除,最后遍历c、a。
这样我们就完美解决了之前说的在相邻的重复想删除了之后又有相邻项,完成继续删除的操作。
- 完成删除重复项之后,我们栈内的元素和我们的目标字符串不是反的么?(画个图 比较好理解这句话的意思)
其实我们可以用字符串来模拟栈,然后把出口(栈顶)作为字符串的尾巴,把栈底作为字符串的头部。这样这个字符串不就是目标字符串了么。也就是对于字符串“abbac”。我们在字符串模拟的栈里面的结果是
尾 [ a c ] 头 尾[a c]头 尾[ac]头
伪代码
string resultStack;
for (char s : S)
{
//我们遍历的时候首先就和栈内元素做比较,如果这个栈本来就是空的或者该字符不等于字符串的尾部
if (stack.empty() || s != resutStack.push_back())
//此时我把元素放入字符串的尾部
result.push_back(s);
else resultStack.pop_back(); //从尾部弹出
}
return resultStack
150. 逆波兰表达式求值
文字链接:150. 逆波兰表达式求值
逆波兰表达式即后缀表达式,以及如何使用栈来对其进行计算,直接去看视频链接。
这里直接给出伪代码
伪代码
stack<int> st;
for(i = 0; i < s.size(); i++)
{
//遍历字符串,遇见数字加入到栈里,遇见了操作符就从栈内取两个元素进行操作
if (s[i] == '+' || == '-' || =='/' || == '*')
{ //存入两个数字,并且拿到他们的值
num1 = st.top(); st.pop();
num2 = st.top(); st.pop();
//开始运算
if(s[i] == +) st.push(num1 + nums2);
if(s[i] == -) st.push(num1 - nums2);
if(s[i] == *) st.push(num1 * nums2);
if(s[i] == /) st.push(num1 / nums2);
}
else st.push(s[i]);
result = st.top();
st.pop();
return result;
}
C++代码
class Solution {
public:
int evalRPN(vector<string>& tokens) {
// 力扣修改了后台测试数据,需要用longlong
stack<long long> st;
for (int i = 0; i < tokens.size(); i++) {
if (tokens[i] == "+" || tokens[i] == "-" || tokens[i] == "*" || tokens[i] == "/") {
long long num1 = st.top();
st.pop();
long long num2 = st.top();
st.pop();
if (tokens[i] == "+") st.push(num2 + num1);
if (tokens[i] == "-") st.push(num2 - num1);
if (tokens[i] == "*") st.push(num2 * num1);
if (tokens[i] == "/") st.push(num2 / num1);
} else {
st.push(stoll(tokens[i]));
}
}
int result = st.top();
st.pop(); // 把栈里最后一个元素弹出(其实不弹出也没事)
return result;
}
};