一、数据结构
- 栈
特点:先进后出,后进先出;只允许在一端插入和删除数据;
时间复杂度:入栈和出栈的时间复杂度为O(1);
常见应用:函数调用栈、表达式求值、括号匹配等。
- 队列
特点:先进先出;入队:放一个元素到队列尾部;出队:从队列头部取出一个元素;
时间复杂度:入队和出队的时间复杂度为O(1)
常见应用:
-
阻塞队列
队列为空的时候,从队头取数据会被阻塞,直到队列中有了数据才能返回;如果队列已经满了,那么插入数据的操作就会被阻塞,直到队列中有空闲位置后再插入数据,然后再返回。
-
并发队列
同一时刻仅允许一个存或者取操作。
二、题目
1、队列的最大值
思路分析:
需要实现一个时间复杂度是O(1)的入队、出队函数,可以采用双端队列;还需要实现一个时间复杂度为O(1)的max_value()函数,也就是需要另外记录队列中的最大元素,记为MAX1,为了防止MAX1出队后最大元素的信息丢失,所以还需记录次大元素MAX2,以此类推,MAX1,MAX2...MAXN,可以另外维护一个单调递减的队列。
如下图,记数据队列为qdata,辅助队列为qmax:
这里也许会有个问题:遇到入队元素value>qmax.peekLast(),就从队尾删除元素直到不满足条件,会不会导致一些次大元素被删除?
比如,当入队元素为3的时候,就要从qmax中删除元素2、2,会不会在这之后需要获取2作为max_value()函数的返回值? 答案是不会。因为把3入队之后,如果3作为max_value,然后被删除了,并不需要队列前面的2作为次大元素,因为在3被出队之前,2已经出队了。
理清思路之后,就可以实现代码了。
class MaxQueue {
// 数据队列
Deque<Integer> qdata ;
// 辅助队列
Deque<Integer> qmax ;
public MaxQueue() {
qdata = new LinkedList<>();
qmax = new LinkedList<>();
}
// 从辅助队列的队列头取元素返回
public int max_value() {
if(qmax.isEmpty())
return -1;
else return qmax.peekFirst();
}
// 更新数据队列和辅助队列
public void push_back(int value) {
qdata.addLast(value);
// 维持一个单调栈
while(!qmax.isEmpty()&&qmax.peekLast()<value){
qmax.removeLast();
}
qmax.addLast(value);
}
public int pop_front() {
if(qdata.isEmpty())
return -1;
// 如果当前出队的元素在辅助队列中,辅助队列的队列头元素也删除
if(!qmax.isEmpty()&&qdata.peekFirst().equals(qmax.peekFirst())){
qmax.removeFirst();
}
// 删除并返回数据队列的队列头元素
int value = qdata.peekFirst();
qdata.removeFirst();
return value;
}
}
2、包含最小值的栈
思路分析:
实现思路和上一题大同小异,有两点不同:1、使用的数据结构是栈;2、维护的辅助栈是单调递增的;
代码实现:
class MinStack {
// 需要借助一个辅助栈记录每次产生的最小元素
Stack<Integer> s_data;
Stack<Integer> s_min;
/** initialize your data structure here. */
public MinStack() {
s_data = new Stack<>();
s_min = new Stack<>();
}
public void push(int x) {
if(s_min.isEmpty()||x<=s_min.peek()){
s_min.push(x);
}
s_data.push(x);
}
public void pop() {
// 弹出的元素在辅助栈里面,辅助栈的栈顶元素也需要弹出
if(s_data.pop().equals(s_min.peek()))
s_min.pop();
}
public int top() {
return s_data.peek();
}
public int min() {
return s_min.peek();
}
}