栈与队列
注:本文代码来自于代码随想录
1.用栈实现队列 232
C++
class MyQueue {
public:
stack<int> stIn; //两个 int 类型的栈,分别用来处理队列的输入和输出。
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;
}
/*首先检查 stOut 是否为空。如果为空,就将 stIn 栈中的所有元素转移到 stOut 中,这样就可以实现队列的顺序。
从 stOut 栈顶弹出一个元素并返回。result 存储弹出的元素,stOut.pop() 删除这个元素。*/
/** Get the front element. */
int peek() {
int res = this->pop(); // 直接使用已有的pop函数
stOut.push(res); // 因为pop函数弹出了元素res,所以再添加回去
return res;
}
/*使用 pop 方法获取队列头部的元素并存储在 res 中。
将 res 重新推回 stOut 栈中,因为 pop 方法会弹出元素。
返回 res*/
/** Returns whether the queue is empty. */
bool empty() {
return stIn.empty() && stOut.empty();
}
/*empty 方法用于检查队列是否为空。只有当 stIn 和 stOut 都为空时,队列才为空,返回 true。否则返回 false。*/
};
Python
需要注意区分Python的pop()和我们自定义的pop()!!!
class MyQueue:
def __init__(self):
"""
in主要负责push,out主要负责pop
"""
self.stack_in = []
self.stack_out = []
def push(self, x: int) -> None:
"""
有新元素进来,就往in里面push
"""
self.stack_in.append(x)
def pop(self) -> int:
"""
Removes the element from in front of queue and returns that element.
"""
if self.empty():
return None
if self.stack_out:
# 如果 stack_out 有元素,直接从 stack_out 中弹出并返回一个元素。这里的 pop 是 Python 内置的列表的 pop 方法,用于移除并返回列表的最后一个元素。
return self.stack_out.pop()
else:
# 如果 stack_out 没有元素,将 stack_in 的所有元素逐个弹出并压入 stack_out。然后,从 stack_out 中弹出并返回一个元素。
for i in range(len(self.stack_in)):
self.stack_out.append(self.stack_in.pop())
return self.stack_out.pop()
def peek(self) -> int:
"""
Get the front element.
"""
ans = self.pop()
self.stack_out.append(ans) # 只是想查看一下,还要推回去
return ans
def empty(self) -> bool:
"""
只要in或者out有元素,说明队列不为空
"""
return not (self.stack_in or self.stack_out)
2.用队列实现栈 225
可以用两个队列实现,代码见卡哥的代码随想录。
一个队列在模拟栈弹出元素的时候只要将队列头部的元素(除了最后一个元素外) 重新添加到队列尾部,此时再去弹出元素就是栈的顺序了。
C++
class MyStack {
public:
queue<int> que;
/** Initialize your data structure here. */
MyStack() {
}
/** Push element x onto stack. */
void push(int x) {
que.push(x);
}
/** Removes the element on top of the stack and returns that element. */
int pop() {
int size = que.size();
size--;
while (size--) { // 将队列头部的元素(除了最后一个元素外) 重新添加到队列尾部
que.push(que.front());
que.pop();
}
int result = que.front(); // 此时弹出的元素顺序就是栈的顺序了
que.pop();
return result;
}
/** Get the top element. */
int top() {
return que.back();
}
/** Returns whether the stack is empty. */
bool empty() {
return que.empty();
}
};
Python
掌握Python的deque()类型,掌握popleft()
1. popleft()
是 Python 内置的操作吗?是用来操作列表的吗?
popleft()
不是操作列表的内置方法,而是 collections.deque
对象的方法。Python 的内置列表没有 popleft
方法。
deque
是双端队列(double-ended queue)的缩写,是 Python collections
模块中提供的一种数据结构,支持在两端高效地添加和删除元素。deque
的 popleft()
方法用于移除并返回队列左端(也就是队首)的元素。
2. deque
是什么数据结构?
deque
(双端队列)是一种通用的队列数据结构,支持在两端高效地添加和删除元素。它的特性包括:
- 高效的添加和删除操作:在两端添加或删除元素的时间复杂度为 O(1)。
- 灵活性:既可以用作队列(FIFO),也可以用作栈(LIFO)。
在 Python 中,deque
是 collections
模块的一部分,使用如下方式导入:
from collections import deque
3 代码里面的栈是什么,Python 真的有这个数据结构吗?
在这个代码中,我们用 deque
来模拟栈的行为。Python 本身没有专门的栈数据结构,但可以用列表或 deque
来实现栈的功能。栈是一种 LIFO(后进先出)数据结构,通常支持以下操作:
push
:添加元素到栈顶。pop
:移除并返回栈顶元素。peek
(或top
):返回栈顶元素但不移除它。empty
:检查栈是否为空。
在 Python 中,列表可以用作栈,使用 append()
和 pop()
方法来实现 push
和 pop
操作,但 deque
更高效。
注意:
append
方法默认将元素添加到队尾(右边)。
popleft
方法默认从队首(左边)移除并返回元素。
deque 中的队首和队尾
- 队首(Head):
deque
的队首默认在左边。- 在左边进行的操作,比如
appendleft()
和popleft()
,是针对队首的。
- 队尾(Tail):
deque
的队尾默认在右边。- 在右边进行的操作,比如
append()
和pop()
,是针对队尾的。
from collections import deque
dq = deque([1, 2, 3, 4, 5])
print(dq) # 输出:deque([1, 2, 3, 4, 5])
dq.popleft() # 移除并返回左端元素 1
print(dq) # 输出:deque([2, 3, 4, 5])
dq.pop() # 移除并返回右端元素 5
print(dq) # 输出:deque([2, 3, 4])
class MyStack:
def __init__(self):
self.que = deque()
"""
初始化了一个 deque 类型的成员变量 self.que。deque 是 Python 集合模块中的双端队列(double-ended queue),它支持在两端快速添加和删除元素。
"""
def push(self, x: int) -> None:
self.que.append(x)
def pop(self) -> int:
# 移除并返回栈顶元素
if self.empty():
# 检查队列是否为空,如果为空返回 None
return None
for i in range(len(self.que)-1):
"""
将队列中的元素(除了最后一个元素)依次移到队列的右端。这是通过循环 len(self.que) - 1 次实现的,每次将队首元素移到队尾(self.que.popleft() 移除队首元素并返回,self.que.append() 将其添加到队尾)
"""
self.que.append(self.que.popleft())
# 最后一个元素此时在队首,调用 popleft 移除并返回它。
return self.que.popleft()
def top(self) -> int:
# 获取栈顶元素,但不移除它。
# 写法一:
# if self.empty():
# return None
# return self.que[-1] # 返回队列的最后一个元素
# 写法二:
if self.empty():
return None
for i in range(len(self.que)-1):
# 将队列中的元素(除了最后一个元素)依次移到队列的右端
self.que.append(self.que.popleft())
temp = self.que.popleft()
# 弹出最后一个元素并存储在 temp 中,将其重新添加到队列末尾,返回 temp
self.que.append(temp)
return temp
def empty(self) -> bool:
# 检查栈是否为空。
return not self.que
3.有效的括号 20
括号匹配是使用栈解决的经典问题。
有三种不匹配的情况:
- 第一种情况,字符串里左方向的括号多余
- 第二种情况,括号没有多余,但是 括号的类型没有匹配上
- 第三种情况,字符串里右方向的括号多余
使用栈的方式:扫描到左括号,就让对应的右括号入栈。扫描到右括号,就在栈里查找,栈顶的元素是不是它,相同就出栈,不相同则为不匹配。
动画见代码随想录网站本题解析
第一种情况:已经遍历完了字符串,但是栈不为空,说明有相应的左括号没有右括号来匹配,所以return false
第二种情况:遍历字符串匹配的过程中,发现栈里没有要匹配的字符。所以return false
第三种情况:遍历字符串匹配的过程中,栈已经为空了,没有匹配的字符了,说明右括号没有找到对应的左括号return false
那么什么时候说明左括号和右括号全都匹配了呢,就是字符串遍历完之后,栈是空的,就说明全都匹配了。
C++
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();
}
};
Python
栈
仅使用栈,更省空间
class Solution:
def isValid(self, s: str) -> bool:
stack = []
for item in s:
if item == '(':
stack.append(')')
elif item == '[':
stack.append(']')
elif item == '{':
stack.append('}')
elif not stack or stack[-1] != item:
""" not stack:如果栈是空的,意味着没有匹配的左括号。
stack[-1] != item:如果栈顶元素(最近的左括号的对应右括号)不是当前字符,说明括号不匹配。"""
return False
else:
"""如果栈不为空且栈顶元素与当前右括号匹配,则弹出栈顶元素,继续检查下一个字符。"""
stack.pop()
return True if not stack else False
# 如果所有字符处理完后,栈为空,说明所有括号匹配,返回 True;否则返回 False。
字典
class Solution:
def isValid(self, s: str) -> bool:
stack = []
mapping = {
'(': ')',
'[': ']',
'{': '}'
}
for item in s:
if item in mapping.keys():
stack.append(mapping[item])
elif not stack or stack[-1] != item:
return False
else:
stack.pop()
return True if not stack else False
4.删除字符串中的所有相邻重复项 1047
C++
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()); // 此时字符串需要反转一下
return result;
}
};
拿字符串直接作为栈,这样省去了栈还要转为字符串的操作。
class Solution {
public:
string removeDuplicates(string S) {
string result;
for(char s : S) {
if(result.empty() || result.back() != s) {
result.push_back(s);
}
else {
result.pop_back();
}
}
return result;
}
};
Python:
使用栈
# 方法一,使用栈
class Solution:
def removeDuplicates(self, s: str) -> str:
res = list()
for item in s:
if res and res[-1] == item:
# 栈不为空而且栈顶元素与item相等,消除
res.pop()
else:
res.append(item)
return "".join(res) # 字符串拼接
使用双指针模拟栈
# 方法二,使用双指针模拟栈,如果不让用栈可以作为备选方法。
class Solution:
def removeDuplicates(self, s: str) -> str:
res = list(s)
slow = fast = 0
length = len(res)
while fast < length:
# 如果一样直接换,不一样会把后面的填在slow的位置
res[slow] = res[fast]
# 如果发现和前一个一样,就退一格指针
if slow > 0 and res[slow] == res[slow - 1]:
slow -= 1
else:
slow += 1
fast += 1
return ''.join(res[0: slow])
5.逆波兰表达式求值 150
注意:题目要求结果是整数
逆波兰表达式主要有以下两个优点:
- 去掉括号后表达式无歧义,上式即便写成 1 2 + 3 4 + * 也可以依据次序计算出正确结果。
- 适合用栈操作运算:遇到数字则入栈;遇到运算符则取出栈顶两个数字进行计算,并将结果压入栈中。
- 逆波兰表达式相当于是二叉树中的后序遍历。 把运算符作为中间节点,按照后序遍历的规则可以画出一个二叉树。
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;
}
};
Python
from operator import add, sub, mul
def div(x, y):
# 使用整数除法的向零取整方式
return int(x / y) if x * y > 0 else -(abs(x) // abs(y))
class Solution(object):
op_map = {'+': add, '-': sub, '*': mul, '/': div}
def evalRPN(self, tokens: List[str]) -> int:
stack = []
for token in tokens:
if token not in {'+', '-', '*', '/'}:
stack.append(int(token))
else:
op2 = stack.pop()
op1 = stack.pop()
stack.append(self.op_map[token](op1, op2)) # 第一个出来的在运算符后面
return stack.pop()
另一种可行,但因为使用eval相对较慢的方法:
from operator import add, sub, mul
def div(x, y):
# 使用整数除法的向零取整方式
return int(x / y) if x * y > 0 else -(abs(x) // abs(y))
class Solution(object):
op_map = {'+': add, '-': sub, '*': mul, '/': div}
def evalRPN(self, tokens):
"""
:type tokens: List[str]
:rtype: int
"""
stack = []
for token in tokens:
if token in self.op_map:
op1 = stack.pop()
op2 = stack.pop()
operation = self.op_map[token]
stack.append(operation(op2, op1))
else:
stack.append(int(token))
return stack.pop()
return int(x / y) if x * y > 0 else -(abs(x) // abs(y))
需要注意
int(x/y)是向0取整,x//y是向下取整,我们自定义的div是向0取整的整除法函数
6.滑动窗口最大值 239
C++
class Solution {
private:
class MyQueue { //单调队列(从大到小)
public:
deque<int> que; // 使用deque来实现单调队列
// 每次弹出的时候,比较当前要弹出的数值是否等于队列出口元素的数值,如果相等则弹出。
// 同时pop之前判断队列当前是否为空。
void pop(int value) {
if (!que.empty() && value == que.front()) {
que.pop_front();
}
}
// 如果push的数值大于入口元素的数值,那么就将队列后端的数值弹出,直到push的数值小于等于队列入口元素的数值为止。
// 这样就保持了队列里的数值是单调从大到小的了。
void push(int value) {
while (!que.empty() && value > que.back()) {
que.pop_back();
}
que.push_back(value);
}
// 查询当前队列里的最大值 直接返回队列前端也就是front就可以了。
int front() {
return que.front();
}
};
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
MyQueue que;
vector<int> result;
for (int i = 0; i < k; i++) { // 先将前k的元素放进队列
que.push(nums[i]);
}
result.push_back(que.front()); // result 记录前k的元素的最大值
for (int i = k; i < nums.size(); i++) {
que.pop(nums[i - k]); // 滑动窗口移除最前面元素
que.push(nums[i]); // 滑动窗口前加入最后面的元素
result.push_back(que.front()); // 记录对应的最大值
}
return result;
}
};
Python
from collections import deque
class MyQueue: #单调队列(从大到小
def __init__(self):
self.queue = deque() #这里需要使用deque实现单调队列,直接使用list会超时
#每次弹出的时候,比较当前要弹出的数值是否等于队列出口元素的数值,如果相等则弹出。
#同时pop之前判断队列当前是否为空。
def pop(self, value):
if self.queue and value == self.queue[0]:
self.queue.popleft()#list.pop()时间复杂度为O(n),这里需要使用collections.deque()
#如果push的数值大于入口元素的数值,那么就将队列后端的数值弹出,直到push的数值小于等于队列入口元素的数值为止。
#这样就保持了队列里的数值是单调从大到小的了。
def push(self, value):
while self.queue and value > self.queue[-1]:
self.queue.pop()
self.queue.append(value)
#查询当前队列里的最大值 直接返回队列前端也就是front就可以了。
def front(self):
return self.queue[0]
class Solution:
def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
que = MyQueue()
result = []
for i in range(k): #先将前k的元素放进队列
que.push(nums[i])
result.append(que.front()) #result 记录前k的元素的最大值
for i in range(k, len(nums)):
que.pop(nums[i - k]) #滑动窗口移除最前面元素
que.push(nums[i]) #滑动窗口前加入最后面的元素
result.append(que.front()) #记录对应的最大值
return result
7.前 K 个高频元素 347
C++
class Solution {
public:
// 小顶堆
class mycomparison {
public:
bool operator()(const pair<int, int>& lhs, const pair<int, int>& rhs) {
return lhs.second > rhs.second;
}
};
vector<int> topKFrequent(vector<int>& nums, int k) {
// 要统计元素出现频率
unordered_map<int, int> map; // map<nums[i],对应出现的次数>
for (int i = 0; i < nums.size(); i++) {
map[nums[i]]++;
}
// 对频率排序
// 定义一个小顶堆,大小为k
priority_queue<pair<int, int>, vector<pair<int, int>>, mycomparison> pri_que;
// 用固定大小为k的小顶堆,扫面所有频率的数值
for (unordered_map<int, int>::iterator it = map.begin(); it != map.end(); it++) {
pri_que.push(*it);
if (pri_que.size() > k) { // 如果堆的大小大于了K,则队列弹出,保证堆的大小一直为k
pri_que.pop();
}
}
// 找出前K个高频元素,因为小顶堆先弹出的是最小的,所以倒序来输出到数组
vector<int> result(k);
for (int i = k - 1; i >= 0; i--) {
result[i] = pri_que.top().first;
pri_que.pop();
}
return result;
}
};
Python
class Solution {
public:
// 小顶堆
class mycomparison {
public:
bool operator()(const pair<int, int>& lhs, const pair<int, int>& rhs) {
return lhs.second > rhs.second;
}
};
vector<int> topKFrequent(vector<int>& nums, int k) {
// 要统计元素出现频率
unordered_map<int, int> map; // map<nums[i],对应出现的次数>
for (int i = 0; i < nums.size(); i++) {
map[nums[i]]++;
}
// 对频率排序
// 定义一个小顶堆,大小为k
priority_queue<pair<int, int>, vector<pair<int, int>>, mycomparison> pri_que;
// 用固定大小为k的小顶堆,扫面所有频率的数值
for (unordered_map<int, int>::iterator it = map.begin(); it != map.end(); it++) {
pri_que.push(*it);
if (pri_que.size() > k) { // 如果堆的大小大于了K,则队列弹出,保证堆的大小一直为k
pri_que.pop();
}
}
// 找出前K个高频元素,因为小顶堆先弹出的是最小的,所以倒序来输出到数组
vector<int> result(k);
for (int i = k - 1; i >= 0; i--) {
result[i] = pri_que.top().first;
pri_que.pop();
}
return result;
}
};