栈和队列的定义
栈和队列的区别:栈是先进后出/后进先出(LIFO),队列是先进先出(FIFO)。
栈
python
Python 没有专门的栈类,通常使用 列表(list) 来模拟栈,因为列表的 append() 和 pop() 操作在末尾进行,时间复杂度 O(1),符合栈的 LIFO 特性。
python
stack = [] # 创建一个空栈
stack.append(1) # 压栈:元素添加到末尾
stack.append(2)
top = stack[-1] # 查看栈顶元素(不弹出)
popped = stack.pop() # 弹栈:移除并返回末尾元素
is_empty = not stack # 判断是否为空
size = len(stack) # 获取栈大小
C++
C++ 标准库提供了**std::stack** 容器适配器,默认基于**deque** 实现,也可指定底层容器(如 vector、list)。
需要包含头文件 :#include <stack>
cpp
#include <stack>
using namespace std;
stack<int> st; // 创建一个存储 int 的空栈
st.push(1); // 压栈
st.push(2);
int top = st.top(); // 查看栈顶元素(不弹出)
st.pop(); // 弹栈(无返回值)
bool empty = st.empty(); // 判断是否为空
int size = st.size(); // 获取栈大小
队列
python
Python 中可以使用 collections.deque(双端队列)来实现高效队列,因为列表的 pop(0) 是 O(n) 操作,不适合做队列。deque 的 append() 和 popleft() 都是 O(1)。
python
from collections import deque
q = deque() # 创建一个空队列
q.append(1) # 入队:从右端添加
q.append(2)
front = q[0] # 查看队首元素(不弹出)
popped = q.popleft() # 出队:从左端移除并返回
is_empty = not q # 判断是否为空
size = len(q) # 获取队列大小
注意 :deque 也可以从左边添加 appendleft,从右边弹出 pop,实现双端队列。
C++
C++ 标准库提供了 std::queue 容器适配器,默认基于 deque 实现。
需要包含头文件 :#include <queue>
cpp
#include <queue>
using namespace std;
queue<int> q; // 创建一个存储 int 的空队列
q.push(1); // 入队:添加到末尾
q.push(2);
int front = q.front(); // 查看队首元素(不弹出)
int back = q.back(); // 查看队尾元素(不弹出)
q.pop(); // 出队:移除队首元素(无返回值)
bool empty = q.empty(); // 判断是否为空
int size = q.size(); // 获取队列大小
双向队列(deque)
C++
是的,deque 是 双端队列(double-ended queue) 的缩写。它支持在头部和尾部高效地插入和删除元素(时间复杂度 O(1)),同时也能通过下标随机访问元素。
不过要注意:
-
双端队列是一种逻辑上的数据结构,强调的是"两端可操作"。
-
双向链表 (如
std::list)则是另一种数据结构,强调的是"每个节点有前后指针,可在任意位置快速插入/删除"。
在 C++ 中,std::deque 底层是分段连续数组(不是链表),因此它既能双端操作,又能随机访问;而**std::list 才是真正的双向链表,不能随机访问**。
所以回答你的问题:deque 是双端队列,但不是双向链表。如果你需要"双向队列"这个词的准确定义,它就是双端队列。
python
-
从右端添加 :
append(x)------ 类似push_back或栈的push。 -
从左端添加 :
appendleft(x)------ 类似push_front。 -
从右端弹出 :
pop()------ 类似栈的pop或队列的出队(从右)。 -
从左端弹出 :
popleft()------ 类似队列的出队(从左)
python
from collections import deque
q = deque()
q.append(1) # 右端添加
q.appendleft(2) # 左端添加
right = q.pop() # 右端弹出
left = q.popleft() # 左端弹出
底层实现
-
Python :栈使用
list;队列使用collections.deque(双端队列)或queue.Queue(线程安全,但较慢)。 -
C++ :
stack和queue都是容器适配器,底层默认用deque,也可以手动指定vector或list。例如:
cpp
stack<int, vector<int>> st; // 用 vector 作为底层容器
queue<int, list<int>> q; // 用 list 作为底层容器
push以及push_back的使用(C++)
顺序容器(vector, deque, list)
顺序容器是直接管理元素存储的数据结构,它们提供:
-
对元素的直接访问(如迭代器、下标等)
-
插入、删除等操作(可能特定位置效率不同)
-
底层内存管理由容器自身负责
C++ 中的顺序容器包括:
-
vector:动态数组,尾部插入快,支持随机访问。 -
deque:双端队列,两端插入/删除快,也支持随机访问(但底层是分段连续存储,并非链表)。 -
list:双向链表,任意位置插入/删除快,不支持随机访问。 -
array:固定大小数组。 -
forward_list:单向链表。
deque 是顺序容器 ,因为它直接管理元素存储,并提供了完整的容器接口(如 begin()、end()、push_back、push_front、operator[] 等),底层虽然是分段连续内存,但对用户透明。
这些容器支持在尾部 添加元素,使用 push_back;有些还支持在头部 添加,使用 push_front(如 deque、list)。
cpp
#include <vector>
#include <deque>
#include <list>
std::vector<int> vec;
vec.push_back(1); // 尾部添加
std::deque<int> dq;
dq.push_back(1); // 尾部
dq.push_front(2); // 头部
std::list<int> lst;
lst.push_back(1);
lst.push_front(2);
为什么叫 push_back? 因为"back"指容器的末尾,这些容器是线性序列,强调前后位置。
容器适配器(stack, queue, priority_queue)
容器适配器不是独立的容器 ,而是对底层容器(默认是 deque)的接口进行封装和限制,提供特定的行为(如栈、队列)。它们不提供直接的元素访问或迭代器,只提供受限的操作:
-
stack:后进先出(LIFO),只能从一端操作。 -
queue:先进先出(FIFO),只能从一端入、另一端出。 -
priority_queue:按优先级出队,通常用堆实现。
适配器的底层容器可以是任何支持所需操作的顺序容器(如 vector、deque、list),默认是 deque。例如:
cpp
stack<int> s1; // 默认使用 deque
stack<int, vector<int>> s2; // 使用 vector 作为底层容器
适配器不提供迭代器、不提供 begin()/end(),因为它们的语义不允许遍历。
deque不是适配器
因为 deque 是一个完整功能的容器,它:
-
支持随机访问(
[]操作符) -
支持在两端高效插入删除(
push_front/pop_front/push_back/pop_back) -
提供迭代器,可以遍历所有元素
而适配器(如 queue) 故意隐藏 了这些功能,只暴露符合队列语义的操作(push、pop、front、back),防止用户误用。所以 deque 是基础工具,适配器是基于它(或其他容器)构建的"限制版"接口。
这些适配器提供的是栈、队列 的语义,而不是直接操作底层容器的"前后"。它们使用统一的 push 方法表示**"插入一个元素"**。
cpp
#include <stack>
#include <queue>
std::stack<int> st;
st.push(1); // 压栈
std::queue<int> q;
q.push(1); // 入队
std::priority_queue<int> pq;
pq.push(1); // 插入优先队列
为什么叫 push? 因为栈和队列的经典操作名就是 push(压入)和 pop(弹出),不强调前后。

常见错误示例
cpp
std::stack<int> st;
st.push_back(1); // ❌ 错误!stack 没有 push_back
st.push(1); // ✅ 正确
std::vector<int> vec;
vec.push(1); // ❌ 错误!vector 没有 push
vec.push_back(1); // ✅ 正确
20.有效的括号

解题思路
本题判断有效括号,核心思路就是要用到栈。
为了防止当栈为空的时候,pop()方法会报错,我们事先在栈中放入"?"元素来避免这种情况。
如果是左括号:( 、[ 、{ ,则压入堆栈中;
如果是右括号: )、]、 },
如果栈为空,则说明栈中没有与之相对应的括号,返回false;
反之则将栈顶元素在字典中所对应的值与之相对应。如若不符,则返回false;反之则弹出栈顶元素,继续遍历当前字符串。
遍历结束后,如果栈的大小为1,说明所有符号都已经匹配上,反之则存在没有匹配上的,返回false。
代码
python
python
class Solution(object):
def isValid(self, s):
"""
:type s: str
:rtype: bool
"""
dict = {'{':'}', '[':']', '(':')', '?':'?'}
stack = ['?']
for c in s:
if c in dict:
stack.append(c)
elif dict[stack.pop()] != c:
return False
return len(stack) == 1
C++
cpp
class Solution {
public:
bool isValid(string s) {
unordered_map<char, char> tmp{{'{', '}'}, {'[', ']'}, {'(', ')'}, {'?', '?'}};
stack<char> st;
st.push('?');
// 先把?存入栈中,避免后续栈为空出现pop而后报错的情况
for(char c : s)
{
// c确实在栈中
if(tmp.count(c))
st.push(c);
else
{
// c和栈顶元素在字典当中所对应的值是否相同?
if(tmp[st.top()] != c)
return false;
// 相同则弹出栈顶元素
st.pop();
}
}
// 如果都匹配的话,最终栈中只有?这一个元素
return st.size() == 1;
}
};
参考
155.最小栈

解题思路
辅助栈
核心就是在原始数据栈的基础上加了一个辅助栈,辅助栈里面存的就是当前数据栈中的最小值(其实就是辅助栈就是一个从小到大的单调栈,栈顶是最小的元素)。
那么具体规则如下:
1.void push(int val) 将元素val推入堆栈:
将val存入数据栈中,同时需要看辅助栈是否为空,或者该值是否小于等于辅助栈的栈顶元素,如果是,则存入辅助栈中;
2.void pop() 删除堆栈顶部的元素:
在数据栈中弹出栈顶元素,几位top_val,如果top_val也等于辅助栈的栈顶元素,那么辅助栈也弹出该元素;
3.int top() 获取堆栈顶部的元素:
直接返回数据栈的栈顶元素即可;
4.int getMin() 获取堆栈中的最小元素
直接返回辅助栈中的最小值即可。
这种同步机制,确保了辅助栈的栈顶始终与当前数据栈的状态同步,记录着当前的最小值,从而让 getMin() 可以在 O(1) 时间内完成。
代码
python
python
class MinStack(object):
def __init__(self):
self.st = [] # 数据栈
self.minst = [] # 辅助栈
def push(self, val):
"""
:type val: int
:rtype: None
"""
self.st.append(val)
# 当辅助栈为空,或者当前值小于辅助栈的最小值则将该值同样压入辅助栈中
if not self.minst or val <= self.minst[-1]:
self.minst.append(val)
def pop(self):
"""
:rtype: None
"""
if self.st:
top_val = self.st.pop()
# 如果当前值同样也是辅助栈中的最小值,则辅助栈也需要将该值弹出
if top_val == self.minst[-1]:
self.minst.pop()
def top(self):
"""
:rtype: int
"""
return self.st[-1] if self.st else None
def getMin(self):
"""
:rtype: int
"""
return self.minst[-1] if self.minst else None
# Your MinStack object will be instantiated and called as such:
# obj = MinStack()
# obj.push(val)
# obj.pop()
# param_3 = obj.top()
# param_4 = obj.getMin()
C++
cpp
class MinStack {
stack<int> st;
stack<int> minst;
public:
MinStack() {
}
void push(int val) {
st.push(val);
// 如果minst为空或者当前值小于等于辅助栈的栈顶值,则存入辅助栈
if(minst.empty() || val <= minst.top())
minst.push(val);
}
void pop() {
int val = st.top();
st.pop();
// 如果数据栈弹出的值和辅助栈的栈顶元素相同,则辅助站也要弹出该值
if(val==minst.top())
minst.pop();
}
int top() {
return st.top();
}
int getMin() {
return minst.top();
}
};
/**
* Your MinStack object will be instantiated and called as such:
* MinStack* obj = new MinStack();
* obj->push(val);
* obj->pop();
* int param_3 = obj->top();
* int param_4 = obj->getMin();
*/
394.字符串解码

解题思路
双栈法
我们用两个栈来解决这个问题,一个是数字栈,一个是字符串栈,其中数字栈存储的是当前累积的数字,而字符串中存储的是当前累积的字符串。
当遇到 ' [ ' 的时候,表示一个嵌套的开始,需要把当前的数字以及字符都压入栈中,重新计数当前数字以及字符,处理内层内容;当遇到 ' ] ' 的时候,表示一个嵌套的结束,需要把之前压入栈中数字以及字符弹出栈,然后将当前字符串重复当前数字次,再拼接到弹出的字符串之后,形成新的当前字符串。
这样当扫描完整个字符串,当前字符串结果就是最终的解码结果。
代码
python
python
class Solution(object):
def decodeString(self, s):
"""
:type s: str
:rtype: str
"""
num_stack = []
str_stack = []
cur_num = 0
cur_str = ""
for ch in s:
# 当前字符是数字
if ch.isdigit():
cur_num = cur_num * 10 + int(ch)
# 当前字符是[,代表嵌套开始,将当前数字以及字符串都压入栈,然后重置
elif ch == '[':
num_stack.append(cur_num)
str_stack.append(cur_str)
cur_num = 0
cur_str = ""
# 代表嵌套结束,将当前字符串重复先前数字次,然后拼接到先前的字符串之后
elif ch == ']':
prev_num = num_stack.pop()
prev_str = str_stack.pop()
cur_str = prev_str + cur_str * prev_num
else:
cur_str += ch
return cur_str
C++
cpp
class Solution {
public:
string decodeString(string s) {
stack<int> num_stack;
stack<string> str_stack;
int cur_num = 0;
string cur_str = "";
for(char ch : s)
{
// 如果当前字符为数字,则存入当前数字中
if(isdigit(ch))
cur_num = cur_num * 10 + (ch - '0');
// 如果当前字符是'[',则将当前字符串以及数字进入压栈,并重置当前字符串以及数字
else if(ch == '[')
{
num_stack.push(cur_num);
str_stack.push(cur_str);
cur_num = 0;
cur_str = "";
}
// 嵌套结束,开始出栈,将当前字符串重复先前数字次数,而后再拼接到先前字符串之前
else if(ch == ']')
{
int prev_num = num_stack.top();num_stack.pop();
string prev_str = str_stack.top();str_stack.pop();
//cur_str = prev_str + cur_str * prev_num;
// 注意C++中没有python那样的直接乘法,需要不断循环追加
for(int i = 0; i< prev_num;i++)
prev_str += cur_str;
cur_str = prev_str;
}
else{
cur_str += ch;
}
}
return cur_str;
}
};
239.滑动窗口最大值

解题思路
本题使用单调队列:
以图上例子举例:
nums = [1, 3, -1, -3 , 5 , 3, 6, 7], k = 3
每次移动窗口,我们要pop()窗口左边的元素,然后push()窗口右边的元素,并且用getMaxValue()得到窗口的最大值。我们要实现的就是这么三个函数。
如果使用暴力解法,对每一个窗口都再进行遍历的话,那么时间复杂度就是o(窗口数目 * k),时间复杂度比较高。
那么如果是使用优先级队列的话,priority_queue,那么第一个窗口[1,3,-1],该窗口的最大值是3,在优先级队列当中是[3,1,-1],当窗口向后移动的时候,我们需要把1给pop,但是此时优先级队列中1在中间,无法直接pop出来,所以这个题目优先级队列不太适配。
因此我们使用单调队列,然后自定义上述三个函数,具体思路如下:
首先我们模拟上述例子的全过程:
nums = [1, 3, -1, -3 , 5 , 3, 6, 7], k = 3
1,3,-1\] 最大值3,单调队列中存储的是最大值,当压入队列当中的值比队列末尾的值要大,队列末尾的值则弹出,也即单调队列最左边一直都是窗口的最大值,所以此时单调队列是\[3, -1
3, -1, -3\],-3\<-1,因此单调队列不需要pop()操作,-3压入单调队列即可\[3, -1, -3\],此时的最大值还是3;
\[-1, -3, 5\], 5大于此前单调队列中的所有值,因此单调队列pop()掉此前单调队列当中的所有值,此时单调队列中为\[\],压入5,最大值为5,单调队列为\[5\];
\[-3, 5, 3\], 3 \< 5,单调队列不需要pop(),3压入单调队列当中,此时的单调队列\[5, 3\],最大值是5;
\[5, 3, 6\],6大于单调队列中的所有元素,因此单调队列需要pop()所有元素,处理之后单调队列为\[\],而后压入push()6,单调队列为\[6\],最大值是6;
\[3, 6, 7\],7大于单调队列中的所有元素,因此单调队列需要pop()所有元素,处理之后单调队列为\[\],而后压入push()7,单调队列为\[7\],最大值是7;
所以结果是\[3, 3, 5, 5, 6, 7\],和图上结果一致;
#### pop()
如果当前需要移除的值等于单调队列的最大值,则执行pop操作;
#### push()
在单调队列当中,队列从左到右按递减次序进行排列,而后窗口向后移动,当要压入的值大于单调队列末尾的值时,需要pop()掉单调队列队尾的值,在压入当前值;
#### getMaxValue()
返回单调队列的首部的值即可。
### 代码
#### python
```python
class Solution(object):
def maxSlidingWindow(self, nums, k):
"""
:type nums: List[int]
:type k: int
:rtype: List[int]
"""
# ans = [0] * (len(nums) -k + 1)
ans = []
dq = deque()
for i in range(len(nums)):
# pop 当前值比单调队列已有值都大则弹出单调队列的比该值小的值
while dq and nums[i] > nums[dq[-1]]:
dq.pop()
# 存下标
dq.append(i)
# push
# 窗口左边元素是i-k+1,但先前窗口左边元素是i-k,
# 当nums[i-k]恰好是单调队列的最大值时,单调队列也需要弹出该值
if i-k == dq[0]:
dq.popleft()
if i >= k - 1:
# ans[i - k + 1] = nums[dq[0]]
ans.append(nums[dq[0]])
return ans
```
#### C++
```cpp
class Solution {
public:
vector