### 理论基础
了解一下 栈与队列的内部实现机制,文中是以C++为例讲解的。
文章讲解:[https://programmercarl.com/%E6%A0%88%E4%B8%8E%E9%98%9F%E5%88%97%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A1%80.html](https://programmercarl.com/%E6%A0%88%E4%B8%8E%E9%98%9F%E5%88%97%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A1%80.html)
### 容器适配器(Container Adapters)
容器适配器是基于基本容器的封装,提供了特定的接口,限制了一些操作,专门用于实现特定的数据结构。它们不能直接实例化,而是基于某种底层容器实现。常见的容器适配器包括:
- **queue**:基于`deque`或`list`实现,提供**先进先出**(FIFO)队列的接口。
- **priority_queue**:基于`vector`实现,提供优先级队列的接口。
- **stack**:基于`deque`或`list`实现,提供**后进先出**(LIFO)堆栈的接口。
#### 1. deque(双端队列)
- **类别**:容器(Sequence Container)。
- **特点**:双端队列,支持在两端进行插入和删除操作,同时支持随机访问。
#### 2. queue(队列)
- **类别**:容器适配器(Container Adapter)。
- **特点**:先进先出(FIFO)队列,基于`deque`或`list`实现,只允许在一端插入,在另一端删除,不支持随机访问。
#### 3. priority_queue(优先队列)
- **类别**:容器适配器(Container Adapter)。
- **特点**:优先级队列,基于`vector`实现,按优先级顺序访问元素,不支持随机访问,只能访问优先级最高的元素。
### 232.用栈实现队列
大家可以先看视频,了解一下模拟的过程,然后写代码会轻松很多。
题目链接/文章讲解/视频讲解:[https://programmercarl.com/0232.%E7%94%A8%E6%A0%88%E5%AE%9E%E7%8E%B0%E9%98%9F%E5%88%97.html](https://programmercarl.com/0232.%E7%94%A8%E6%A0%88%E5%AE%9E%E7%8E%B0%E9%98%9F%E5%88%97.html)
![[Day_9_1.gif]]
1.思路;
使用栈来模式队列的行为,如果仅仅用一个栈,是一定不行的,所以需要两个栈**一个输入栈,一个输出栈**,这里要注意输入栈和输出栈的关系。
在push数据的时候,只要数据放进输入栈就好,**但在pop的时候,操作就复杂一些,输出栈如果为空,就把进栈数据全部导入进来(注意是全部导入)**,再从出栈弹出数据,如果输出栈不为空,则直接从出栈弹出数据就可以了。
最后如何判断队列为空呢?**如果进栈和出栈都为空的话,说明模拟的队列为空了。**
在代码实现的时候,会发现pop() 和 peek()两个函数功能类似,代码实现上也是类似的,可以思考一下如何把代码抽象一下。
2.代码:
```
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. 用队列实现栈
可能大家惯性思维,以为还要两个队列来模拟栈,其实只用一个队列就可以模拟栈了。
建议大家掌握一个队列的方法,更简单一些,可以先看视频讲解
题目链接/文章讲解/视频讲解:[https://programmercarl.com/0225.%E7%94%A8%E9%98%9F%E5%88%97%E5%AE%9E%E7%8E%B0%E6%A0%88.html](https://programmercarl.com/0225.%E7%94%A8%E9%98%9F%E5%88%97%E5%AE%9E%E7%8E%B0%E6%A0%88.html)
1.思路:
**用两个队列que1和que2实现队列的功能,que2其实完全就是一个备份的作用**,把que1最后面的元素以外的元素都备份到que2,然后弹出最后面的元素,再把其他元素从que2导回que1。
![[Day_9_2.gif]]
2.代码(两个队列):
```
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--;//可以用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.
** Can not use back() direactly.
*/
int top(){
int result=this->pop();
que1.push(result);
return result;
}
/** Returns whether the stack is empty. */
bool empty() {
return que1.empty();
}
};
```
代码(1个队列)(队首的元素移到队尾):
```
class MyStack {
public:
queue<int> que1;
/** 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--;//可以用size()来判断留下最后一个元素
while (size--) { // 将que1 导入que2,但要留下最后一个元素
int tmp=que1.front();
que1.pop();
que1.push(tmp);
}
int result = que1.front(); // 留下的最后一个元素就是要返回的值
que1.pop();
return result;
}
/** Get the top element.
** Can not use back() direactly.
*/
int top(){
int result=this->pop();
que1.push(result);
return result;
}
/** Returns whether the stack is empty. */
bool empty() {
return que1.empty();
}
};
```
### 20. 有效的括号
讲完了栈实现队列,队列实现栈,接下来就是栈的经典应用了。
大家先自己思考一下 有哪些不匹配的场景,在看视频 我讲的都有哪些场景,落实到代码其实就容易很多了。
题目链接/文章讲解/视频讲解:[https://programmercarl.com/0020.%E6%9C%89%E6%95%88%E7%9A%84%E6%8B%AC%E5%8F%B7.html](https://programmercarl.com/0020.%E6%9C%89%E6%95%88%E7%9A%84%E6%8B%AC%E5%8F%B7.html)
1.思路:
**括号匹配是使用栈解决的经典问题。** 括号匹配的顺序跟栈的FILO顺序一样
第一种情况:已经遍历完了字符串,但是栈不为空,说明有相应的左括号没有右括号来匹配,所以return false
第二种情况:遍历字符串匹配的过程中,发现栈里没有要匹配的字符。所以return false
第三种情况:遍历字符串匹配的过程中,栈已经为空了,没有匹配的字符了,说明右括号没有找到对应的左括号return false
那么什么时候说明左括号和右括号全都匹配了呢,就是字符串遍历完之后,栈是空的,就说明全都匹配了。
但还有一些技巧,在匹配左括号的时候,右括号先入栈,就只需要比较当前元素和栈顶相不相等就可以了,比左括号先入栈代码实现要简单的多了!
2.代码:
```
class Solution {
public:
bool isValid(string s) {
if (s.size() % 2 != 0) return false; // 如果s的长度为奇数,一定不符合要求
stack<char> st;
for (int i = 0; i < s.size(); i++) {
if (s[i] == '(') st.push(')');
else if (s[i] == '{') st.push('}');
else if (s[i] == '[') st.push(']');
// 第三种情况:遍历字符串匹配的过程中,栈已经为空了,没有匹配的字符了,说明右括号没有找到对应的左括号 return false
// 第二种情况:遍历字符串匹配的过程中,发现栈里没有我们要匹配的字符。所以return false
else if (st.empty() || st.top() != s[i]) return false;
else st.pop(); // st.top() 与 s[i]相等,栈弹出元素
}
// 第一种情况:此时我们已经遍历完了字符串,但是栈不为空,说明有相应的左括号没有右括号来匹配,所以return false,否则就return true
return st.empty();
}
};
```
### 1047. 删除字符串中的所有相邻重复项
栈的经典应用。
要知道栈为什么适合做这种类似于爱消除的操作,因为栈帮助我们记录了 遍历数组当前元素时候,前一个元素是什么。
题目链接/文章讲解/视频讲解:[https://programmercarl.com/1047.%E5%88%A0%E9%99%A4%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E7%9A%84%E6%89%80%E6%9C%89%E7%9B%B8%E9%82%BB%E9%87%8D%E5%A4%8D%E9%A1%B9.html](https://programmercarl.com/1047.%E5%88%A0%E9%99%A4%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E7%9A%84%E6%89%80%E6%9C%89%E7%9B%B8%E9%82%BB%E9%87%8D%E5%A4%8D%E9%A1%B9.html)
代码:
```
class Solution {
public:
string removeDuplicates(string S) {
stack<char> st;
for (char s : S) {
if (st.empty() || s != st.top()) {
st.push(s);
} else {
st.pop(); // s 与 st.top()相等的情况
}
}
string result = "";
while (!st.empty()) { // 将栈中元素放到result字符串汇总
result += st.top();
st.pop();
}
reverse (result.begin(), result.end()); // 此时字符串需要反转一下
//整体时间复杂度是 O(n)(遍历栈元素) + O(n)(反转字符串),即 O(n)。
//不好的方法,因为+=在末尾添加元素时,如果字符串内部已经有足够的空间,通常不会触发新的内存分配。而+每次在开头添加元素时,都会创建一个新的字符串对象,并复制整个 `result`,这涉及大量的内存分配和数据复制。
while (!st.empty()) { // 将栈中元素放到result字符串汇总
result = st.top()+result;
st.pop();
}//O(n)(遍历栈元素),每次操作都是 O(n) 的复制操作,总的时间复杂度是 O(n^2)
return result;
}
};
```