题目一:滑动窗口的最大值
给定一个数组和滑动窗口的大小,请找出所有滑动窗口里的最大值。
例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小为3,那么移动存在6个活动窗口,
它们的最大值分别为{4,4,6,6,6,5}.
思路:
借助一个辅助队列,从头遍历数组,根据如下规则进行入队列或出队列操作:
- 如果队列为空,则当前数字入队列
- 如果当前数字大于队列尾,则删除队列尾,直到当前数字小于等于队列尾,或者队列空,然后当前数字入队列
- 如果当前数字小于队列尾,则当前数字入队列
- 如果队列头超出滑动窗口范围,则删除队列头
这样能始终保证队列头为当前的最大值
以这道题为例:
从头开始遍历数组,我们以下标 i 表示遍历到第几个数字
在开始阶段,队列为空,我们把2入队列,此时i = 0;
i = 1时,num[1] = 3, 由于3大于队列尾2,所以把2移出队列,把3加入队列;
i = 2时,num[2] = 4, 由于4大于3, 所以把3移出队列,把4加入队列,此时滑动窗口刚好经过三个元素,滑动窗口内的最大值就是队列的头元素,也就是4;
i = 3时,num[3] = 2, 因为2小于4,所以把2直接加入队列,此时滑动窗口内的最大值仍然为4;
i = 4时,num[4] = 6, 6大于2和4,所以把2和4移出队列,把6加入到队列中,此时滑动窗口的最大值为6;
i = 5时,num[5] = 2, 因为2小于6,所以把2直接加入队列,此时滑动窗口内的最大值仍然为6;
i = 6时,num[6] = 5, 由于5大于2,所以把2移出队列,把5加入队列中,此时滑动窗口内的最大值仍然为6;
i = 7时,num[7] = 1, 由于6已经不在滑动窗口中了,所以将6从队列头上删除,此时滑动窗口的最大值为5;
遍历结束
那么,如何判断6是否还在滑动窗口中呢,可以通过数组下标进行判断,我们在队列中存储数组的下标而非数值,这样通过判断下标之间的差值是否大于窗口的大小,就可以判断元素是否还在滑动窗口中。
public ArrayList<Integer> maxinWindows(int[] num,int size){
ArrayList<Integer> result = new ArrayList<>(); //数组
if(num==null || num.length==0 || size==0 || size>num.length)
return result;
LinkedList<Integer> queue = new LinkedList<>(); //队列
for(int i=0;i<num.length;i++) {
if(!queue.isEmpty()) {
//如果队列头元素不在滑动窗口中了,就删除头元素
if(i>=queue.peek()+size)
queue.pop();
//如果当前数字大于队列尾,则删除队列尾,直到当前数字小于等于队尾
//或者队尾空
while(!queue.isEmpty() && num[i]>=num[queue.getLast()])
queue.removeLast();
}
queue.offer(i);
//滑动窗口经过三个元素,获取当前的最大值,也就是队列的头元素
if(i+1 >= size)
result.add(num[queue.peek()]);
}
return result;
}
还有一种思路,利用优先队列维持一个大顶堆(通常优先队列都是小顶堆)
a.维持一个max heap(删除滑动窗口最左边的元素,加入新的元素)
b.让最大值位于大顶堆
a步骤的维持max head的时间复杂度是logK。
b步骤的时间复杂度是N *O(1),即O(N)。
总体时间复杂度为O(n*logK)。
public int[] maxSlidingWindow(int[] nums, int k) {
if(!(nums instanceof int[]) || nums == null || nums.length == 0)
//判断传进来的是否为int数组,int数组是否为空,int数组是否没有数据
return new int[0];
PriorityQueue<Integer> pq = new PriorityQueue<Integer>
(Collections.reverseOrder());
int[] max = new int[nums.length + 1 - k];
//nums.length + 1 - k得出该数组nums里面滑动窗口的最大值的个数
for(int i = 0 ;i < nums.length; i++){//遍历
//先判断是否为滑动窗口的最左边,是的话移出优先队列
if(i > k){
//i > k ,表示当前滑动窗口里面有K个数,想要新进来一个数,
//需要移除滑动窗口最左边的数
pq.remove(nums[i -k]);
}
pq.offer(nums[i]);//添加数据
//获取移动窗口的最大值
if(i + 1 >= k){
//i + 1 >= k,当前滑动窗口里面有K个数,所有才会有最大值
max[i + 1 - k] = pq.peek();
//表示当前的max的数组下标i + 1 - k
}
}
return max;
}
题目二:队列的最大值
请定义一个队列并实现函数max得到队列里的最大值,
要求函数max,push_back和pop_front的时间复杂度都是O(1)
思路
与滑动窗口的最大值一题相似,利用一个双端队列来存储当前队列里的最大值以及之后可能的最大值。
在定义题目要求功能的队列时,除了定义一个队列data存储数值,还需额外用一个队列maxmium存储可能的最大值;此外,还要定义一个数据结构,用于存放数据以及当前的index值,用于删除操作时确定是否删除maxmium中最大值。
package jianZhiOffer;
import java.util.ArrayDeque;
/*
* 题目二:队列的最大值
* 请定义一个队列并实现函数max得到队列里的最大值,
* 要求函数max,push_back和pop_front的时间复杂度都是O(1)
*/
public class Demo5902 {
private ArrayDeque<InternalData> data = new ArrayDeque<InternalData>();
private ArrayDeque<InternalData> maximum = new ArrayDeque<InternalData>();
private class InternalData{
int number;
int index;
public InternalData(int number,int index) {
this.number = number;
this.index = index;
}
}
private int curIndex;
public void push_back(int number) {
InternalData curData = new InternalData(number,curIndex);
data.addLast(curData);
while(!maximum.isEmpty() && maximum.getLast().number < number)
maximum.removeLast();
maximum.addLast(curData);
curIndex++;
}
public void pop_front() {
if(data.isEmpty()) {
System.out.println("队列为空,无法删除!");
return ;
}
InternalData curData = data.removeFirst();
if(curData.index==maximum.getFirst().index)
maximum.removeFirst();
}
public int max() {
if(maximum==null) {
System.out.println("队列为空,无法删除!");
return 0;
}
return maximum.getFirst().number;
}
public static void main(String[] args) {
Demo5902 test = new Demo5902();
test.push_back(2);
System.out.println(test.max()==2);
test.push_back(3);
System.out.println(test.max()==3);
test.push_back(1);
System.out.println(test.max()==3); //{2,3,1}
test.pop_front();
System.out.println(test.max()==3); //{3,1}
test.pop_front();
System.out.println(test.max()==1); //{1}
}
}