问题描述:
请定义一个队列并实现函数 max_value 得到队列里的最大值,要求函数max_value、push_back 和 pop_front 的均摊时间复杂度都是O(1)。若队列为空,pop_front 和 max_value 需要返回 -1。
输入:
- ["MaxQueue","push_back","push_back","max_value","pop_front","max_value"]
- [[],[1],[2],[],[],[]]
输出: [null,null,null,2,1,2]
算法思路:
我们知道对于一个普通队列,push_back 和 pop_front 的时间复杂度都是O(1),因此我们直接使用队列的相关操作就可以实现这两个函数。对于 max_value 函数,我们通常会这样思考,即每次入队操作时都更新最大值:
但是当出队时,这个方法会造成信息丢失,即当最大值出队后,我们无法知道队列里的下一个最大值。
为了解决上述问题,我们只需记住当前最大值出队后,队列里的下一个最大值即可。具体方法是使用一个双端队列 deque,在每次入队时,如果 deque队尾元素小于即将入队的元素 value,则将小于 valuevalue 的元素全部出队后,再将 valuevalue 入队;否则直接入队。这时,辅助队列 deque 队首元素就是队列的最大值。
class MaxQueue {
//保存队列所有数据
Deque<Integer> dq = new LinkedList<Integer>();
//保存队列中后出队的最大值
Deque<Integer> decrease = new LinkedList<Integer>();
public MaxQueue() {
}
public int max_value() {
return this.dq.isEmpty()?-1:this.decrease.peekFirst();
}
public void push_back(int value) {
//如果 deque队尾元素小于即将入队的元素 value,则将小于 valuevalue 的元素全部出队后,再将 valuevalue 入队
while(!this.decrease.isEmpty() && this.decrease.peekLast() < value){
this.decrease.removeLast();
}
this.decrease.offerLast(value);
this.dq.offerLast(value);
}
public int pop_front() {
int res = this.dq.isEmpty()?-1:this.dq.peekFirst();
if(!this.dq.isEmpty())
this.dq.removeFirst();
//如果此次出队的是剩余队列中最大元素,则在最大值队列中将该元素弹出
if(!this.decrease.isEmpty() && res == this.decrease.peekFirst()){
this.decrease.removeFirst();
}
return res;
}
}
题目二:155. 最小栈
问题描述:
设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。
- push(x) —— 将元素 x 推入栈中。
- pop() —— 删除栈顶的元素。
- top() —— 获取栈顶元素。
- getMin() —— 检索栈中的最小元素
输入:
- ["MinStack","push","push","push","getMin","pop","top","getMin"]
- [[],[-2],[0],[-3],[],[],[],[]]
输出:
- [null,null,null,null,-3,null,0,-2]
算法思路:
借用一个辅助栈min_stack,用于存获取stack中最小值。
- push()方法: 每当push()新值进来时,如果 小于等于 min_stack栈顶值,则一起push()到min_stack,即更新了栈顶最小值;
- pop()方法: 判断将pop()出去的元素值是否是min_stack栈顶元素值(即最小值),如果是则将min_stack栈顶元素一起pop(),这样可以保证min_stack栈顶元素始终是stack中的最小值。
- getMin()方法: 返回min_stack栈顶即可。
min_stack作用分析:
- min_stack等价于遍历stack所有元素,把升序的数字都删除掉,留下一个从栈底到栈顶降序的栈。
- 相当于给stack中的降序元素做了标记,每当pop()这些降序元素,min_stack会将相应的栈顶元素pop()出去,保证其栈顶元素始终是stack中的最小元素。
复杂度分析:
- 时间复杂度 O(1) :压栈、出栈、获取最小值的时间复杂度都为 O(1) 。
- 空间复杂度 O(N) :包含 N 个元素辅助栈占用线性大小的额外空间。
class MinStack {
Deque<Integer> stack = new LinkedList<Integer>();
Deque<Integer> minValueStack = new LinkedList<Integer>();
/** initialize your data structure here. */
public MinStack() {
}
public void push(int x) {
stack.offerLast(x);
//如果 小于等于 min_stack栈顶值,则一起push()到min_stack,即更新了栈顶最小值;
if(minValueStack.isEmpty() || x <= minValueStack.peekLast())
minValueStack.offerLast(x);
}
public void pop() {
int num = 0;
if(!stack.isEmpty()){
num = stack.peekLast();
stack.removeLast();
//将pop()出去的元素值是否是min_stack栈顶元素值(即最小值),如果是则将min_stack栈顶元素一起pop(),这样可以保证min_stack栈顶元素始终是stack中的最小值
if(!minValueStack.isEmpty() && num == minValueStack.peekLast())
minValueStack.removeLast();
}
}
public int top() {
return stack.peekLast();
}
public int getMin() {
return minValueStack.isEmpty()?Integer.MIN_VALUE:minValueStack.peekLast();
}
}
/**
* Your MinStack object will be instantiated and called as such:
* MinStack obj = new MinStack();
* obj.push(x);
* obj.pop();
* int param_3 = obj.top();
* int param_4 = obj.getMin();
*/
题目三:239. 滑动窗口最大值
问题描述:
给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。返回滑动窗口中的最大值。比如输入:nums = [1,3,-1,-3,5,3,6,7], k = 3,输出:[3,3,5,5,6,7]
- 滑动窗口的位置 最大值
- --------------- -----
- [1 3 -1] -3 5 3 6 7 3
- 1 [3 -1 -3] 5 3 6 7 3
- 1 3 [-1 -3 5] 3 6 7 5
- 1 3 -1 [-3 5 3] 6 7 5
- 1 3 -1 -3 [5 3 6] 7 6
- 1 3 -1 -3 5 [3 6 7] 7
算法思路:
这个题目,算是很经典的题目,我们的滑动窗口主要分为两种,一种的可变长度的滑动窗口,一种是固定长度的滑动窗口,这个题目算是固定长度的代表。今天我们用双端队列来解决我们这个题目,学会了这个题目的解题思想你可以去解决一下两道题目 剑指 Offer 59 - II. 队列的最大值,155. 最小栈,虽然这两个题目和该题类型不同,但是解题思路是一致的,都是很不错的题目,我认为做题,那些考察的很细的,解题思路很难想,即使想到,也不容易完全写出来的题目,才是能够大大提高我们编码能力的题目,希望能和大家一起进步。这个题目我们用到了双端队列,队列里面保存的则为每段滑动窗口的最大值。我们先来了解下双端队列吧,队列我们都知道,是先进先出,双端队列呢?既可以从队头出队,也可以从队尾出队,则不用遵循先进先出的规则。下面我们通过一个动画来了解一下吧。
好啦,我们了解双端队列是什么东东了,下面我们再通过一个动画,来看一下代码的执行过程吧,相信各位一下就能够理解啦。我们就通过题目中的例子来表述。nums = [1,3,-1,-3,5,3,6,7], k = 3
不知道通过上面的例子能不能给各位描述清楚,如果不能的话,我再加把劲,各位看官,请接着往下看。我们将执行过程进行拆解:
- 想将我们第一个窗口的所有值存入单调双端队列中,单调队列里面的值为单调递减的。如果发现队尾元素小于要加入的元素,则将队尾元素出队,直到队尾元素大于新元素时,再让新元素入队,目的就是维护一个单调递减的队列。
- 我们将第一个窗口的所有值,按照单调队列的规则入队之后,因为队列为单调递减,所以队头元素必为当前窗口的最大值,则将队头元素添加到数组中。
- 移动窗口,判断当前窗口前的元素是否和队头元素相等,如果相等则出队。
- 继续然后按照规则进行入队,维护单调递减队列。
- 每次将队头元素存到返回数组里。
- 返回数组
是不是懂啦,再回去看一遍视频吧。祝大家新年快乐,天天开心呀!
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
int[] res = new int[nums.length-k+1];
Deque<Integer> dq = new LinkedList<Integer>();
//先将第一个窗口的元素按单调递减规则存如双端队列中;
for(int i=0; i<k; i++){
while(!dq.isEmpty() && nums[i]>dq.peekLast())
dq.removeLast();
dq.offerLast(nums[i]);
}
res[0]=dq.peekFirst();
//移动窗口
for(int i=1; i< nums.length-k+1; i++){
//双端队列的头元素已经不在当前窗口中
if(dq.peekFirst()==nums[i-1])
dq.removeFirst();
//维护单调递减队列
while(!dq.isEmpty() && nums[i+k-1]>dq.peekLast())
dq.removeLast();
dq.offerLast(nums[i+k-1]);
res[i]=dq.peekFirst();
}
return res;
}
}