功能需求
有一个整数型数组arr和一个大小为w的窗口从数组的最左端滑到最右端,窗口每次向右滑动一个位置。就像是一个滑动的指针,从头指向尾,然后输出窗口中数据的最大值。
例如,一组数据为arr[] = [4,3,5,4,3,3,6,7],窗口大小为w = 3时:
[ 4 3 5 ] 4 3 3 6 7 窗口中最大值为 5
4 [ 3 5 4 ] 3 3 6 7 窗口中最大值为 5
4 3 [ 5 4 3 ] 3 6 7 窗口中最大值为 5
4 3 5 [ 4 3 3 ] 6 7 窗口中最大值为 5
4 3 5 4 [ 3 3 6 ] 7 窗口中最大值为 5
4 3 5 4 3 [ 3 6 7 ] 窗口中最大值为 5
如果数组的长度为n,窗口大小为w,这一共产生n-w-7个窗口的最大值。
要求:请实现一个函数。
输入:整型数组arr,窗口大小为w
输出:一个长度为n-w-1的输入res,res[ i ]表达每一种窗口状态下的最大值。{5,5,5,4,6,7}.
详细解析
如果数据长度为N,窗口大小为w,如果做出时间复杂度O(N*w)的解法是不能让面试官满意的,本题要求面试者想出的时间复杂度O(N)的实现。所以本题的关键在于利用双端队列来实现窗口最大值的更新。首先生成的是双端队列qmax,qmax中存放着数组arr中的下标。
假设:遍历到arr[i],qmax的放入规则为:
1、如果qmax为空,直接把下标i放入qmax,放入过程结束。
2、如果qmax不为空,取出当前qmax队尾放入队尾存放的下标,假设为j。
1)如果arr[j] > arr[i],直接把下标i放入qmax的队尾,放入过程结束。
2)如果arr[j] < arr[i],把j从qmax中弹出,继续qmax的放入规则。
假设遍历到arr[i],qmax的弹出规则为:
如果qmax队头的下标等于i-w,说明当前qmax队头下标已经过期,弹出当前对头下标即可。根据如上的放入的弹出规则,qmax便成了一个维护窗口为w的子数组的最大值更新的结构。举例如下;
1、开始时qmax为空,qmax={}
2、遍历到arr[0] == 4,将下标0放入qmax,qmax= { 0 }。
3、遍历到arr[1] == 3,当前qmax的队尾下标为0,又有arr[0] > arr[1],所以将下标1放入qmax尾部,qmax={ 0 , 1}。
4、遍历到arr[2] == 5,当前qmax的队尾下标为1,又有arr[1] <= arr[2],所以将下标1从qmax的尾部弹出,qmax变成{0}。当前qmax的队尾下标为0,又有arr[0] <= arr[2],所以将下标0从qmax尾部弹出,qmax变成{}。将下标2放入qmax,qmax={2}。此时已经遍历到下标2的位置,窗口arr[0,2]出现,当前qmax对头的下标为2,所以窗口arr[0,2]的最大值为arr[2](即为5)。
5、遍历到arr[3]==4,当前qmax的队尾下标为2,又有arr[2]>arr[3],所以将下标了放入qmax尾部,qmax={2,3}。 窗口arr[1..3]出现, 当前qmax队头的下标为2,这个下标还没有过期,所以窗口ar[1..3]的最大值为arr[2] (即5)。
6、遍历到arr[4]== =3,当前qmax的队尾下标为3, 又有arr[3]>arr[4], 所以将下标4放入qmax尾部,qmax={2,3,4}。 窗口arr[2..4]出现,当前qmax队头的下标为2,这个下标还没有过期,所以窗口arr[2..4]的最大值为arr[2] (即5)。
7、遍历到arr[5]==3,当前qmax的队尾下标为4, 又有arr[4]<= =arr[5],所以将下标4从qmax的尾部弹出,qmax变为{2,3}。当前qmax的队尾下标为了,又有arr[3]>arr[5],所以将下标5放入qmax尾部,qmax= {2,3,5}。窗口arr[3..5]出现,当前qmax队头的下标为2,这个下标已经过期,所以从qmax的头部弹出, qmax变为{3,5}。当前qmax队头的下标为3,这个下标没有过期,所以窗口arr[3..5]的最大值为arr[3] (即4)。
8、遍历到arr[6]==6, 当前qmax的队尾下标为5,又有arr[5]<=arr[6], 所以将下标5从qmax的尾部弹出,qmax变为{3}。当前qmax的队尾下标为3, 又有arr[3]<=arr[6],所以将下标3从qmax的尾部弹出,qmax变为{}。将下标6放入qmax,qmax={6}。窗口ar([..]出现,当前qmax队头的下标为6,这个下标没有过期,所以窗口arr[4..6]的 最大值为arr[6](即6)。
9、 遍历到arr[7]==7, 当前qmax的队尾下标为6,又有arr[6]<=arr[7], 所以将下标6 从qmax的尾部弹出,qmax 变为{}。将下标7放入qmax,qmax={7}。 窗口arr[5..7]出现,当前 qmax队头的下标为7,这个下标没有过期,所以窗口arr[5..7]的最 大值为arr[7](即7)。
10、依次出现的窗口最大值为[5,5,5,4,6,7],在遍历过程中收集起来,最后返回即可。
具体过程参看如下代码中的getMax Window方法。
代码实现
// 移动窗口获取最大值方法
public int[] getMaxWindow(int[] arr,int w) {
// 判断数组信息或者截取长度时候合格
if (arr == null || w < 1 || arr.length < w) {
// 返回方法
return;
}
// 实例化一个链表结构
LinkedList<Integer> qmax = new LinkedList<Integer>();
// 实例化一个int类型数组并且定义长度为执行的次数
int[] res = new int[arr.length - w + 1];
// 定义一个下标常量
int index = 0;
// 循环遍历数组
for (int i = 0 ; i < arr.length ; i++) {
// 判断数组中时候还有数据,或者已经遍历到队尾
while (!qmax.isEmpty() && arr[qmax.peekLast()] <= arr[i]) {
// 移除对头元素
qmax.pollLast();
}
// 队尾添加最后一个元素
qmax.addLast(i);
// 判断对头元素时候为最后一个元素
if (qmax.peekFirst() == i - w) {
// 出队第一个元素
qmax.pollFirst();
}
// 如果移动到最后元素前
if (i >= w - 1) {
// 把得到的数组存入res集合中
res[index++] = arr[qmax.peekFirst()];
}
}
// 返回信息的集合
return res;
}
总结分析
双端队列,将arr中的元素加入res该队列中,若该队列的队尾元素小于等于要加入的元素,则不断的弹出,直到队尾元素大于该元素或者队列为空。此时将该元素的序号加入队列中。同时当 i-w == 队头的序号,则将队头元素弹出。上述过程中, 每个下标值最多进qmax-一次, 出qmax一-次。所以遍历的过程中进出双端队列的操作是时间复杂度为O(N),整体的时间复杂度也为O(N)。写完手工。。睡觉