栈和队列的基本性质和应用
1、栈:先进后出;队列:先进先出。
2、栈和队列在实现结构上可以有数组和链表两种形式。
2.1 数组结构实现较容易。
2.2 用链表结构较复杂,因为牵扯很多指针操作。
栈结构的基本操作
1、从栈顶弹出一个元素:pop操作
2、只访问栈顶元素而不弹出:top或peek操作
3、从栈顶压入一个元素:push操作
4、返回当前栈中元素个数:size操作
队列的基本操作
与栈操作不同的是,push操作为在队尾压入元素,而pop操作是从队头部弹出一个元素。
栈和队列的基本操作,都是时间复杂度为O(1)的操作。
双端队列结构为首尾部都可以压入和弹出元素。
优先级队列为根据元素优先级值,决定元素的弹出顺序。
优先级队列的结构为堆结构,并不是线性表结构。
与栈和队列有关的两种图的遍历方式——深度优先遍历(DFS)和宽度优先遍历(BFS)
深度优先遍历(DFS)可以用栈实现
宽度优先遍历(BFS)可以用队列实现
平时使用的递归函数实际上使用了系统提供的函数栈,递归函数的处理过程可以看做是递归函数依次进入函数栈的处理过程,所以任何使用递归函数做的过程都可以用非递归的方式实现。
一、可查询最值的栈
定义栈的数据结构,请在该类型中实现一个能够得到栈最小元素的min函数。
要求:pop, push, getMin操作的时间复杂度都是O(1);设计的栈类型可以使用现成的栈结构。
class Solution {
public:
stack<int> stack_data;
stack<int> stack_min;
void push(int value) {
stack_data.push(value);
if(stack_min.empty() || value<stack_min.top()) stack_min.push(value);
else stack_min.push(stack_min.top());
}
void pop() {
if(!stack_data.empty()){
stack_data.pop();
stack_min.pop();
}
}
int top() {
return stack_data.top();
}
int min() {
return stack_min.top();
}
};
二、双栈队列
编写一个类,只能用两个栈结构实现队列,支持队列的基本操作(push,pop)。
给定一个操作序列ope及它的长度n,其中元素为正数代表push操作,为0代表pop操作,保证操作序列合法且一定含pop操作,请返回pop的结果序列。
测试样例:
[1,2,3,0,4,0],6
返回:[1,2]
【分析】使用两个栈,StackPush栈只压入数据,StackPop栈只弹出数据。把数据依次压入StackPush,再把StackPush中的数据倒入StackPop,再从StackPop中依次弹出数据。
注意:
(1)如果StackPush要往StackPop中倒入数据,那么必须要把StackPush中所有的数据一次性倒完;
(2)如果StackPop中有数据,则不能发生倒数据的行为。
class TwoStack {
public:
vector<int> twoStack(vector<int> ope, int n) {
stack<int> stackpush;
stack<int> stackpop;
vector<int> ans;
for(int i=0;i<=n-1;i++){
if(ope[i]!=0) stackpush.push(ope[i]);
else{
if(!stackpop.empty()){
int temp=stackpop.top();
stackpop.pop();
ans.push_back(temp);
}
else{
while(!stackpush.empty()){
int temp=stackpush.top();
stackpush.pop();
stackpop.push(temp);
}
int temp=stackpop.top();
stackpop.pop();
ans.push_back(temp);
}
}
}
return ans;
}
};
三、栈的反转
实现一个栈的逆序,但是只能用递归函数和这个栈本身的pop操作来实现,而不能自己申请另外的数据结构。
给定一个整数数组A即为给定的栈,同时给定它的大小n,请返回逆序后的栈。
测试样例:
[4,3,2,1],4
返回:[1,2,3,4]
class StackReverse {
public:
vector<int> reverseStack(vector<int>& A, int n);
private:
int get(vector<int>& A);
};
//delete and return the bottom
int StackReverse::get(vector<int>& A){
int res = A.back();
A.pop_back();
if(A.empty()) return res;
else{
int val = get(A);
A.push_back(res);
return val;
}
}
//reverse every element in the stack
vector<int> StackReverse::reverseStack(vector<int>& A, int n){
if(A.empty()) return A;
int val=get(A);
reverseStack(A,n);
A.push_back(val);
return A;
}
四、双栈排序
请编写一个程序,按升序对栈进行排序(即最大元素位于栈顶),要求最多只能使用一个额外的栈存放临时数据,但不得将元素复制到别的数据结构中。
给定一个int[] numbers(C++中为vector<int>),其中第一个元素为栈顶,请返回排序后的栈。请注意这是一个栈,意味着排序过程中你只能访问到第一个元素。
测试样例:
[1,2,3,4,5]
返回:[5,4,3,2,1]
【分析】将想要排序的栈记为Stack,申请的辅助栈记为help。在Stack上执行pop操作,弹出的元素记为current。
if current<=help.top() -> help.push_back(current)
else -> help元素逐渐弹出并压回Stack,直到current<=help.top(),将current压入help中
直到Stack中的元素都压入到help中,最后将help中的元素重新压回到Stack中,就完成排序了。
class TwoStacks {
public:
vector<int> twoStacksSort(vector<int> numbers) {
vector<int> help;
while(!numbers.empty()){
int cur=numbers.back();
numbers.pop_back();
while(!help.empty() && cur>help.back()){
numbers.push_back(help.back());
help.pop_back();
}
help.push_back(cur);
}
return help;
}
};
五、滑动窗口
有一个整型数组 arr 和一个大小为 w 的窗口从数组的最左边滑到最右边,窗口每次向右边滑一个位置。 返回一个长度为n-w+1的数组res,res[i]表示每一种窗口状态下的最大值。 以数组为[4,3,5,4,3,3,6,7],w=3为例。因为第一个窗口[4,3,5]的最大值为5,第二个窗口[3,5,4]的最大值为5,第三个窗口[5,4,3]的最大值为5。第四个窗口[4,3,3]的最大值为4。第五个窗口[3,3,6]的最大值为6。第六个窗口[3,6,7]的最大值为7。所以最终返回[5,5,5,4,6,7]。
给定整形数组arr及它的大小n,同时给定w,请返回res数组。保证w小于等于n,同时保证数组大小小于等于500。
测试样例:
[4,3,5,4,3,3,6,7],8,3
返回:[5,5,5,4,6,7]
【分析】普通解法的时间复杂度为O(N*w),也就是每次对每一个窗口遍历其中的w个数,选出最大值。最优解可以做到时间复杂度O(N)。本题关键在于利用双端队列实现窗口最大值的更新。
双端队列qmax={},双端队列存放着数组中的下标值。假设当前数为arr[i],放入规则如下:
1、如果qmax为空,直接把下标i放入qmax中;
2、如果qmax不为空,取出当前qmax队尾存放的下标j。如果arr[j]>arr[i],直接把下标i放进qmax的队尾。
3、如果arr[j]<=arr[i],则一直从qmax的队尾弹出下标,直到某一个下标在qmax中对应的值大于arr[i],把i放入qmax的队尾。
假设当前数为arr[i],弹出规则如下:
如果qmax队头的下标等于i-w,弹出qmax当前队头下标。
class SlideWindow {
public:
vector<int> slide(vector<int> arr, int n, int w) {
vector<int> res(n-w+1);
deque<int> qmax;
int idx=0;
for(int i=0;i<=n-1;i++){
while(!qmax.empty() && arr[qmax.back()]<arr[i]){
qmax.pop_back();
}
qmax.push_back(i);
if(qmax.front()==i-w) qmax.pop_front();
if(i>=w-1) res[idx++]=arr[qmax.front()];
}
return res;
}
};
六、数组变树
对于一个没有重复元素的整数数组,请用其中元素构造一棵MaxTree,MaxTree定义为一棵二叉树,其中的节点与数组元素一一对应,同时对于MaxTree的每棵子树,它的根的元素值为子树的最大值。现有一建树方法,对于数组中的每个元素,其在树中的父亲为数组中它左边比它大的第一个数和右边比它大的第一个数中更小的一个。若两边都不存在比它大的数,那么它就是树根。请设计O(n)的算法实现这个方法。
给定一个无重复元素的数组A和它的大小n,请返回一个数组,其中每个元素为原数组中对应位置元素在树中的父亲节点的编号,若为根则值为-1。
测试样例:
[3,1,4,2],4
返回:[2,0,-1,2]
【分析】证明该方法的正确性:
(1)该方法可以生成一棵树,而不是森林。
(2)生成的这一棵树是二叉树,而不是多叉树。<-任何一个数在单独一侧,孩子的数量都不超过一个。
方法证明有效后,利用栈得到每个数左右两边第一个比它大的数。
class MaxTree {
public:
vector<int> buildMaxTree(vector<int> A, int n) {
vector<int> res;
vector<int> leftMax(n);
vector<int> rightMax(n);
stack<int> left,right;
for(int i=0;i<=n-1;i++){
while(!left.empty() && A[left.top()]<A[i]){
left.pop();
}
if(left.empty()) leftMax[i]=-1;
else leftMax[i]=left.top();
left.push(i);
}
for(int i=n-1;i>=0;i--){
while(!right.empty() && A[right.top()]<A[i]){
right.pop();
}
if(right.empty()) rightMax[i]=-1;
else rightMax[i]=right.top();
right.push(i);
}
for(int i=0;i<=n-1;i++){
if(leftMax[i]==-1 && rightMax[i]==-1) res.push_back(-1);
else if(leftMax[i]==-1) res.push_back(rightMax[i]);
else if(rightMax[i]==-1) res.push_back(leftMax[i]);
else if(A[leftMax[i]]<A[rightMax[i]]) res.push_back(leftMax[i]);
else res.push_back(rightMax[i]);
}
return res;
}
};