Day64:滑动窗口的最大值

时间:2022-07-24
本文章向大家介绍Day64:滑动窗口的最大值,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

剑指Offer_编程题——滑动窗口的最大值

题目描述:

给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,那么一共存在6个滑动窗口,他们的最大值分别为{4,4,6,6,6,5}; 针对数组{2,3,4,2,6,2,5,1}的滑动窗口有以下6个: {[2,3,4],2,6,2,5,1}, {2,[3,4,2],6,2,5,1}, {2,3,[4,2,6],2,5,1}, {2,3,4,[2,6,2],5,1}, {2,3,4,2,[6,2,5],1}, {2,3,4,2,6,[2,5,1]}。

具体要求:

时间限制: C/C++ 1秒,其他语言2秒 空间限制: C/C++32M,其他语言64M

具体实现:

背景知识介绍:   在做题之前,我们首先给大家介绍队列中的双端队列。 我们知道普通队列是限制级的一端进,另一端出的FIFO形式,栈是一端进出的LIFO形式,而双端队列就没有这样的限制级,也就是我们可以在队列两端进行插入或者删除操作。 通常情况下,队列的内部都是采用数组来实现,而且带有两个指针head和tail来指向数组的区间段,为了充分利用数组空间,我们也会用%来实现逻辑上的循环数组,如下图。

思路一:   根据我们上面对双向队列的解释,另外题目也说的很明白,因此,我们首先想到的就是采用双端队列,其时间复杂度为O(n)。队列的开头保存着当前队列最大值的指针,当队列移动一位时:先判断指针是否超出当前边界;新加入指针和最后队列最后的元素比较,剔除小于新加入指针对应位置的值得指针。我们可以通过java和python两种方式将其实现: 1、首先我们用java将其实现:

import java.util.*;
public class Solution{
	public ArrayList<Integer> maxInWindows(int []num, int size){
		ArrayList<Integer> res = new ArrayList<>();
		int len = num.length;
		if(num == null || len < 1 || size < 1 || size > len)
			return res;
		LinkedList<Integer> llist = new LinkedList<>();
		for(int i = 0; i < len; i++){
			while(!llist.isEmpty() && num[llist.peekLast()] < num[i])
				llist.pollLast();
			llist.addLast(i);
			if(llist.peekFirst() == i - size){
				llist.pollFirst();
			}
			if (i >= size - 1){
				res.add(num[llist.peekFirst()]);
			}
		}
			return res;
	}
}

代码效果图如图所示:

2、接下来我们用python将其实现:

class Solution:
	def maxInWindows(self, num, size):
		if size == 0 or len(num) == 0:
			return []
		q = []
		result = []
		for i in range(len(num)):
			print('current q', q)
			if len(q) > 0:
				if(i - q[0]) > (size - 1):
					q.pop(0)
				while len(q) > 0  and num[i] >= num[q[-1]]:
					q.pop(-1)
			q.append(i)
			if i >= size - 1:
				result.append(num[q[0]])
		return result

代码效果图如图所示:

思路二:   仍然是用到思路一中的双向队列,不过我们加入了Deque容器,队列中只存放当前元素的下标,设新来的元素为k,如果前面的元素比k小,直接把前面的删除(因为不可能成为后面窗口的最大值),如果前面的元素比k大,判断是否还在窗口范围内,不在则移除;首先先判断当前队列是否为空,如果不空而且当前元素比队列中尾端的元素大,将队列元素的尾端弹出;最后判断队列头元素(存的是下标)是否还在滑动窗口范围中,不在则把头元素移除;具体我们用java实现如下:

import java.util.*;
public class Solution{
	public ArrayList<Integer> maxInWindows(int []num, int size){
		ArrayList<Integer> arr = new ArrayList<>();
		if (num == null)
			return arr;
		if(num.length < size || size <= 0)
			return arr;
		Deque<Integer> queue = new LinkedList<>();
		for(int i = 0; i < num.length; i++){
			while(!queue.isEmpty() && num[i] >= num[queue.getLast()]){
				queue.pollLast();
			}
			while(!queue.isEmpty() && queue.getFirst() < i - (size - 1))
				queue.pollFirst();
			queue.offerLast(i);
			if(i + 1 >= size)
				arr.add(num[queue.getFirst()]);
		}
		return arr;
	}
}

代码效果图如图所示:

思路三:   昨天我们在做数据流中的中位数的时候,用到了优先队列(PriorityQueue),并且给大家详解介绍了优先队列的用法,如果大家不懂得可以看这篇文章。本题,我们也可以用优先队列。通过生成javabean方法,其中包括:set、get方法来实现堆排序,具体可以在滑动窗口滑动时,将当前元素加入最大堆(PriorityQueue)中,堆顶的元素即是最大值,同时还要判断当前元素是否存在于当前窗口中,不存在的话弹出,最后就把该元素添加入Arraylist。我们用java将其实现:

import java.util.*;
class tmp{
	public tmp(){
	}
	public tmp(Integer num, Integer pos){
		this.pos = pos;
		this.num = num;
	}
	public Integer pos;
	public Integer num;
	public Integer getNum() {
        return num;
    }
    public void setNum(Integer num) {
        this.num = num;
    }
    public Integer getPos() {
        return pos;
    }
    public void setPos(Integer pos) {
        this.pos = pos;
    }  
}
public class Solution{
	public ArrayList<Integer> maxInWindows(int [] num, int size){
		ArrayList<Integer> arr = new ArrayList<>();
		if(num == null)
			return arr;
		if(num.length < size || size <= 0)
			return arr;
		PriorityQueue<tmp> q = new PriorityQueue<>(100, new Comparator<tmp>(){
			public int compare(tmp o1, tmp o2){
				return o2.num - o1.num;
			}
		});
		for(int i = 0; i < size - 1; i++)
			q.offer(new tmp(num[i], i));
		for(int i = size - 1; i < num.length; i++){
			q.offer(new tmp(num[i], i));
			tmp p = q.peek();
			while(p.getPos() < i - (size - 1)){
				q.poll();
				p = q.peek();
			}
			arr.add(p.getNum());
		}
		return arr;
	}
}

代码效果图如图所示:

思路四:   本题可以定义一个求最大值的max函数,窗口每一次滑动求窗口内最大值,时间复杂度O(n*size)。窗口的滑动过程中数字的进出类似一个队列中元素的出队入队,这里我们采用一个队列queue存储可能成为最大值的元素下标(之所以队列中存元素下标而不是元素值本身,是因为队列并不存储所有元素,而我们需要知道什么时候队首元素已经离开滑动窗口)。当遇到一个新数时,将它与队尾元素比较,如果大于队尾元素,则丢掉队尾元素,继续重复比较,直到新数小于队尾元素,或者队列为空为止,将新数下标放入队列。同时需要根据滑动窗口的移动判断队首元素是否已经离开。例如:下面以{2,3,6,2,1,7,3,1,5,2},大小为4的窗口模拟过程:

  算法的实现如下,最好的情况是当数组降序排列,此时新数永远比队尾元素大,直接存入队列,时间复杂度为O(n),最坏的情况是当数组是升序排列,此时新数永远比队列中所有元素都大,每次都需要清空队列,时间复杂度为O(nk)。空间复杂度是O(k).我们具体用python将其实现:

class Solution:
	def maxInWindows(self, num, size):
		maxqueue = []
		maxlist = []
		n = len(num)
		if n == 0 or size == 0 or size > n:
			return maxlist
		for i in range(n):
			if len(maxqueue) > 0 and i - size >= maxqueue[0]:
				maxqueue.pop(0)
			while len(maxqueue) > 0 and num[i] > num[maxqueue[-1]]:
				maxqueue.pop()
			maxqueue.append(i)
			if i >= size - 1:
				maxlist.append(num[maxqueue[0]])
		return maxlist

代码效果图如图所示:

思路五:   该思路其实也是在思路四的基础上进行的改进,思路四是自己定义一个求最大值max函数。其实python中就有max函数,我们可以直接利用max函数计算其函数值。具体用python实现如下:

class Solution:
	def maxInWindows(self, num, size):
		if size > len(num) or size == 0:
			return []
		result = []
		for i in range(len(num)):
			if (i+size) > len(num):
				break
			current_win = num[i:i+size]
			value = max(current_win)
			result.append(value)
		return result

代码效果图如图所示:

思路六:   其实我们也可以用一种最简单最暴力的方法。每移动一次,遍历数组获取最大值。但是时间复杂度太高。接下来我们分别用java和python将其实现: 1、首先我们用java将其实现:

import java.util.*;
public class Solution{
	public ArrayList<Integer> maxInWindows(int [] num, int size){
		ArrayList<Integer> list = new ArrayList<>();
		int len = num.length;
		if(num == null || len < 1 || size < 1 || size > len)
			return list;
		for(int i = size - 1; i < len; i++){
			int max = getMaxInWindows(num, i - size + 1, i, size);
			list.add(max);
		}
		return list;
	}
	public int getMaxInWindows(int []num, int start, int end, int size){
		int max = num[start];
		for(int i = start + 1; i <= end; i++){
			if(num[i] > max){
				max = num[i];
			}
		}
		return max;
	}
}

代码效果图如图所示:

2、接下来用python将其实现:

class Solution:
	def maxInWindows(self, num, size):
		if size <= 0:
			return []
		res = []
		for i in range(0, len(num)-size+1):
			res.append(max(num[i:i+size]))
		return res

代码效果图如图所示:

总结

  本题主要通过找出滑动窗口的最大值来考察我们对双向队列的掌握和理解。本文在做题之前给大家介绍了双向队列的相关内容,另外本文给出了六种解题思路。首先就是用到的双向队列,并且分别用java和python两门编程语言将其实现。其次我们用到了昨天介绍的优先队列来构建堆来对数据进行排序,并且在用优先队列前,我们生成java bean 方法,包括生成无参和有参构造器以及生成set、get方法。还有就是我们用到了python中的max方法以及我们最后直接用简单粗暴的方法也可以做出来,不过就是复杂度过高。因此,我们在做题的时候,应该多次尝试各种方法,扩展自己的思维,写出优质的代码。总之,我们要继续加油,争取早日找到工作,Good Luck!!!

参考文献

[1] 经典算法19–双端队列 [2] sniperken [3] 一颗随风而倒的墙头草 [4] Max_7 [5] justlikeu777 [6] 今天敲代码了么