面试题:队列的最大值
题目一:滑动窗口的最大值
给定一个数组和滑动窗口的大小,找出所有滑动窗口里的最大值
如:{2,3,4,2,6,2,5,1}及滑动窗口的大小3,则最大值分别为{4,4,6,6,6,5}
题目二:队列的最大值
定义一个队列,并实现函数max得到队列里的最大值,要求函数max、push_back和pop_front的时间复杂度都是O(1)
思路:
题目一:
1. 暴力解法:k个窗口的最大值时间复杂度O(k),n个窗口则需要O(nk)
2. 实际上,一个滑动窗口可以看做一个队列,当窗口滑动时,处于窗口的第一个数字被删除,同时在窗口的末尾添加一个新的数字——符合队列的先进先出特性,因此,如果能从队列中找出它的最大数,那么也可解决这个问题——在面试题30:包含min函数的栈 https://blog.csdn.net/qq_22527013/article/details/89739627 中,使用了两个栈来实现一个队列,用此方法可用O(1)的时间得到队列的最大值,总的时间复杂度也降到了O(n)
3. 上面一种思路中在面试过程中时间可能不够用,可换种思路
并不把滑动窗口的每个数值都存入队列,而是只把有可能成为滑动窗口最大值的数值存入一个两端开口的队列。队列头部存放滑动窗口的最大值。每当窗口滑动一个位置,会判断一个新的数是否要加入到队列中——
1)如果新的数大于当前的最大值,则将队列清空,将这个新的数加入到窗口中;
2)如果新的数小于当前的最大值,则将其加入到队列中,以在最大值退出窗口后准备使用
1)的原因:因为队列中的数为之前滑动窗口中可能为最大值的数,但是这个新的数会在k时间内一直在滑动窗口中,直到前面的数都消失,所以就算在这段时间内,当前的最大值被滑动到窗口外,窗口中的最大值也不可能是小于这个新数的数。
如1 2 3 4 5 6 7 8,当滑动窗口在1234时,队列中只存放4,当遇到5时,将队列清空,放入5,这是因为无论在2345还是3456还是4567中,5都是大于之前的旧最大值的,并且在5进入队列中开始,5的持续时间比4的长,所以4不可能在5没有消失的时候成为最大值,但是4又比5消失得更早,因此4不可能为之后的最大值,所以直接从最大值保存队列中删除
2)的原因:队列中的最大值如果离开窗口后,需要次大值来作为当前的最大值,每次只有一个数离开窗口,因此需要保存小于当前最大值的数
方法:
用双向队列
队列头部存放最大值,其他位置存放小于最大值的数
当进入一个新数时,先与最大值比较——如果大于最大值,清空队列,并将新数放入队列中;
如果新数小于最大值,则与待用值比较——如果大于队列尾部元素,则从尾部删除元素,直到尾部元素大于新数为止,再将新数加入到队列尾部
注意:由于窗口滑动,所以可能有值会退出窗口,因此,队列中保存数在数组中的位置,如果位置超出窗口范围,也从队列中删除
题目二:
类似题目一
代码:
public class Q59_1 {
public static void main(String[] args) {
int[] a = new int[] {2,3,4,2,6,2,5,1};
int[] re = getMax(a,3);
for(int i=0;i<re.length;i++) {
System.out.println(re[i]);
}
}
public static int[] getMax(int[] a,int k) {
if(a.length<k || k<1) {
return null;
}
Deque<Integer> deque = new LinkedList<Integer>();
int[] max = new int[a.length-k+1];
int loc = 0;
// 前k个单独处理
for(int i=0;i<k;i++) {
if(deque.isEmpty()) {
deque.addFirst(i);
}else {
// 新数大于最大值——清空队列,插入到头部
if(a[i]>a[deque.peekFirst()]) {
// 题目一中的清空操作可不用
while(!deque.isEmpty()) {
deque.pop();
}
deque.addFirst(i);
// 新数小于最大值——从尾部开始删除小于新数的数,并插入到尾部
}else {
while(a[deque.peekLast()]<a[i]) {
deque.removeLast();
}
deque.addLast(i);
}
}
}
max[loc++] = a[deque.peekFirst()];
for(int i=k;i<a.length;i++) {
// 新数大于最大值——清空队列,插入到头部
if(a[i]>a[deque.peekFirst()]) {
while(!deque.isEmpty()) {
deque.removeFirst();
}
deque.addFirst(i);
// 新数小于最大值——从尾部开始删除小于新数的数,并插入到尾部
}else {
while(a[deque.peekLast()]<a[i]) {
deque.removeLast();
}
deque.addLast(i);
}
// 删除退出窗口欧的数
deque.remove(i-k);
// 在最大值的数组中加入该位置窗口的最大值
max[loc++] = a[deque.peekFirst()];
}
return max;
}
}
题目二:
// 类
public class queue_59 {
Deque<Integer> max;
Queue<Integer> queue;
// 初始化
public queue_59() {
max = new LinkedList<Integer>();
queue = new LinkedList<Integer>();
}
// 删除
public void pop_front(){
if(queue.isEmpty()) {
System.out.println("cannot remove from null");
}else {
int temp = queue.remove();
max.remove(temp);
}
}
// 添加数字
public void push_back(int x){
queue.add(x);
if(max.isEmpty()) {
max.add(x);
}else {
if(x>max.getFirst()) {
while(!max.isEmpty()) {
max.remove();
}
max.add(x);
}else {
while(max.peekLast()<x) {
max.removeLast();
}
max.addLast(x);
}
}
}
// 返回最大值
public int getMax() {
if(max.isEmpty()) {
System.out.println("no number in max-list");
return -1;
}else {
return max.peekFirst();
}
}
}
// 测试方法
public class Q59_2 {
public static void main(String[] args) {
queue_59 queue = new queue_59();
queue.push_back(2);
queue.pop_front();
queue.push_back(3);
queue.pop_front();
queue.push_back(4);
System.out.println(queue.getMax());
queue.push_back(2);
queue.push_back(6);
System.out.println(queue.getMax());
queue.pop_front();
queue.pop_front();
queue.pop_front();
queue.pop_front();
System.out.println(queue.getMax());
queue.push_back(2);
queue.push_back(5);
System.out.println(queue.getMax());
queue.pop_front();
queue.pop_front();
queue.pop_front();
queue.push_back(1);
System.out.println(queue.getMax());
}
}