栈与队列问题
栈是数据结构的基础,也是解决问题的重要方法。
💗 STL中的栈与队列
在STL中,stack表示栈,Queue表示队列。
(1).stack
stack是通过序列容器来实现的。stack主要有以下几个特点:
① stack严格遵循后进先出(LIFO)原则,因此stack不提供元素的任何迭代器操作,不会向外部提供可用的前向或后向迭代器类型。
② stack只能通过top从栈顶获取和删除数据,不能遍历,不支持随机存储。
③ stack的常用操作:
使用时注意包含头文件 #include <stack>
s.empty() //如果栈为空返回true,否则返回false
s.size() //返回栈中元素的个数
s.pop() //删除栈顶元素但不返回其值
s.top() //返回栈顶的元素,但不删除该元素
s.push(X) //在栈顶压入新元素 ,参数X为要压入的元素
(2).queue
queue是通过序列容器来实现的。queue主要有以下几个特点:
① queue遵循先入先出(FIFO)原则,queue不提供元素的任何迭代器操作,不会向外部提供可用的前向或后向迭代器类型。
② queue只能从一端插入,另一端删除,不能遍历,不支持随机存储。
③ queue的常用操作:
使用时注意包含头文件 #include <queue>
q.empty()// 如果队列为空返回true,否则返回false
q.size() // 返回队列中元素的个数
q.pop() //删除队列首元素但不返回其值
q.front() // 返回队首元素的值,但不删除该元素
q.push(X) //在队尾压入新元素 ,X为要压入的元素
q.back() //返回队列尾元素的值,但不删除该元素
1. 数据栈与辅助栈解决问题
数据栈和辅助栈就是利用两个栈,其中一个栈来存放数据,另一个栈用来计算另一些数据(栈的最大值,最小值等)。通过牺牲空间来换取时间。
🐟 ① 155. 最小栈==========================================================================================
设计一个支持 push,pop,top 操作,并能在常数时间内检索到最小元素的栈。
push(x) – 将元素 x 推入栈中。 pop() – 删除栈顶的元素。 top() – 获取栈顶元素。getMin() – 检索栈中的最小元素。
输入 | 输出 | 解释 |
---|---|---|
MinStack minStack = new MinStack(); minStack.push(-2); minStack.push(0); minStack.push(-3); minStack.getMin(); --> 返回 -3. minStack.pop(); minStack.top(); --> 返回 0. minStack.getMin(); --> 返回 -2. |
思路:此题要求要在常数时间内检索到最小元素的栈,因此,无法通过入栈后在判断最小元素的方式,因此,需要添加辅助栈。辅助栈用来存放每次遇到更小的元素,这样就能保证在辅助栈中的元素是当前栈中最小的元素。
class MinStack {
public:
stack<int> s;
stack<int> minStack;
/** initialize your data structure here. */
MinStack() {
}
void push(int x) {
s.push(x); //数据栈入栈
if(minStack.empty() || x<=minStack.top()) //如果辅助栈为空或当前值小于辅助栈栈顶元素,则入辅助栈。
minStack.push(x);
}
void pop() {
if(s.top()==minStack.top()) //如果数据栈的栈顶元素与辅助栈的栈顶元素相同时,
//则同时将栈顶元素出栈,否则只对数据栈出栈
minStack.pop();
s.pop();
}
int top() {
return s.top();
}
int getMin() { //辅助栈的栈顶元素即为当前栈中的最小元素。
return minStack.top();
}
};
2. 队列与栈的转换(相互实现)
此类问题通常包括用队列来实现栈或用栈来实现队列。
🐟 ① 225. 用队列实现栈=====================================================================================
使用队列实现栈的下列操作:
push(x) – 元素 x 入栈 pop() – 移除栈顶元素 top() – 获取栈顶元素 empty() – 返回栈是否为空
思路:用队列实现栈有三种方式:
(1).利用二个队列,压入(push)时间复杂度为O(1),弹出(pop)时间复杂度为O(n).。
压入(push):直接将数据放入到队列q1
当中。
弹出(pop):添加一个临时队列
q2
,将
q1
队列的前
n-1
个数据,入队到
q2
中,然后弹出
q1
的最后一个元素。再将
q1
与
q2
交换。
class MyStack {
public:
/** Initialize your data structure here. */
queue<int> que;
queue<int> helper; //临时辅助队列
int temp;
MyStack() {
}
/** Push element x onto stack. */
void push(int x) {
que.push(x); //压入(push)直接将数据入队q1
}
/** Removes the element on top of the stack and returns that element. */
int pop() {
while(que.size()>1){ //将q1队列中前n-1个数据,入队到q2中
int temp=que.front();
helper.push(temp);
que.pop();
}
int ans=que.front();
que.pop(); //弹出q1的最后一个元素
queue<int> temp=helper;
helper=que;
que=temp; //将q1与q2交换
return ans;
}
/** Get the top element. */
int top() {
while(que.size()>1){
temp=que.front();
helper.push(temp);
que.pop();
}
int ans=que.front();
helper.push(ans);
que.pop();
queue<int> temp=helper;
helper=que;
que=temp;
return ans;
}
};
(2).利用二个队列,压入(push)时间复杂度为O(n),弹出(pop)时间复杂度为O(1).。
压入(push):让每一个新元素从 q2
入队,同时把这个元素作为栈顶元素保存。当 q1
非空(也就是栈非空),我们让 q1
中所有的元素全部出队,再将出队的元素从 q2
入队。通过这样的方式,新元素(栈中的栈顶元素)将会在 q2
的前端。我们通过将 q1
, q2
互相交换的方式来避免把 q2
中的元素往 q1
中拷贝。
q1
当中弹出即可。
class MyStack {
public:
/** Initialize your data structure here. */
queue<int> que;
queue<int> helper;
int temp;
MyStack() {
}
/** Push element x onto stack. */
void push(int x) {
helper.push(x);
while(!que.empty()){ //当 `q1` 非空(也就是栈非空),我们让 `q1` 中所有的元素全部出队,再将出队的元素从 `q2` 入队。
int temp=que.front();
helper.push(temp);
que.pop();
}
queue<int> temp_q=helper;
helper=que;
que=temp_q;
}
/** Removes the element on top of the stack and returns that element. */
int pop() {
int ans=que.front(); //直接从q1中出队
que.pop();
return ans;
}
/** Get the top element. */
int top() {
int ans=que.front();
return ans;
}
};
(3).利用一个队列,压入(push)时间复杂度为O(n),弹出(pop)时间复杂度为O(1).。
压入(push):每当入队一个新元素的时候,我们可以把队列的顺序反转过来。
q1
当中弹出即可。
class MyStack {
public:
/** Initialize your data structure here. */
queue<int> que;
queue<int> helper;
int temp;
MyStack() {
}
/** Push element x onto stack. */
void push(int x) {
que.push(x);
int len=que.size();
while(len>1){
int temp=que.front(); //每当入队一个新元素的时候,我们可以把队列的顺序反转过来
que.push(temp);
que.pop();
len--;
}
}
/** Removes the element on top of the stack and returns that element. */
int pop() {
int ans=que.front();
que.pop();
return ans;
}
/** Get the top element. */
int top() {
int ans=que.front();
return ans;
}
};
3. 单调栈
所谓单调栈,就是栈中存放的数据是有序的,单调栈分为单调递增栈和单调递减栈。注意:这里说的递增递减指的是出栈的顺序,而不是在栈中数据的顺序。
① 单调递增栈:数据出栈的顺序是单调递增序列。
② 单调递减栈:数据出栈的顺序是单调递减序列。
🐟 ① 496. 下一个更大元素 I==================================================================================
给定两个没有重复元素的数组 nums1 和 nums2 ,其中nums1 是 nums2 的子集。找到 nums1 中每个元素在 nums2 中的下一个比其大的值。
nums1 中数字 x 的下一个更大元素是指 x 在 nums2 中对应位置的右边的第一个比 x 大的元素。如果不存在,对应位置输出-1。
输入 | 输出 | 解释 |
---|---|---|
nums1 = [4,1,2], nums2 = [1,3,4,2] | [-1,3,-1] | 对于num1中的数字4,你无法在第二个数组中找到下一个更大的数字,因此输出 -1。 对于num1中的数字1,第二个数组中数字1右边的下一个较大数字是 3。 对于num1中的数字2,第二个数组中没有下一个更大的数字,因此输出 -1。 |
思路:利用单调栈,依次单调递增(出栈单调递减)的求取更大的元素,然后利用unordered_map存储对应元素与下一个更大元素。若当前元素cur大于栈顶(当前元素pre),则将(key,value)=(pre,cur)存入map,并出栈。将当前元素入栈。
class Solution {
public:
vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {
vector<int> ans;
if(nums1.size()>nums2.size() || nums2.size()<=0){
return ans;
}
stack<int> s;
unordered_map<int,int> m;
s.push(nums2[0]); /将第一个元素入栈
for(int i=1;i<nums2.size();i++){
while(!s.empty()){
if(nums2[i]>s.top()){ 如果当前元素cur大于栈顶元素,则说明上一个元素pre的下一个最大元素为cur,则将(key,value)=(pre,cur)存入map
m[s.top()]=nums2[i];
s.pop(); //将当前栈顶清空,出栈过程,出栈元素数值递减
}else
break;
}
s.push(nums2[i]); //将当前元素cur入栈
}
while(!s.empty()){ //栈中其余元素是没有下一个更大元素的值
m[s.top()]=-1;
s.pop();
}
for(int i=0;i<nums1.size();i++){ //在子集nums1中遍历,查找对应map中的下一更大元素,放入vector中。
ans.push_back(m[nums1[i]]);
}
return ans;
}
};