给定一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。
返回滑动窗口中的最大值。
分析
为了透彻分析这类问题,我会把大脑中 “智能”分析的过程 与 实际编码 一步步对应起来。这个分析的过程非常的重要,题目千千万,而分析过程所用的思想就那些,掌握了基本分析思想,就可以触类旁通。
- 对于第一次接触这类题的小伙伴看完题目后可能立马就思考,这个 “滑动” 到底如何实现呢?发出这个问题的同时,想必在大脑中也会一直模拟题目给的样例,来反复滑动,试图找到一种合适的方式来实现这个 “滑动”。
- 我们大脑思考的这个 “滑动” 的过程实际上是个非常 “智能” 的过程,想怎么滑动就怎么滑动。但是,对应具体实现程序来说就得一步步来,不能像我们大脑所想象的那样——可以跳动的滑动。
- 首先第一步就是选择合适的数据结构,根据题目很明显就可以判断,要在一个线性数组中进行处理,与线性数组有关的容易想到 栈和 队列,那么这题用栈是不合适的,因为这不是单调栈的题目,那就选择队列,队列只是存储运算过程中的结果,并不是借助队列实现滑动。
滑动处理
其实在大脑中模拟这个滑动的过程中,我们其实就已经有了如何 处理 “滑动” 这两个字的过程了。
- 第一步:既然是在数组中滑动,那么具体处理 “滑动” 的实现自然就是数组遍历了
- 第二步:有了滑动的实现,再结合题目的限制条件——维护一个 k 长度的窗口,即维护一个长度为k的数组
- 第三步:那么如何边滑动边维护呢?既然是遍历数组滑动,那肯定要通过 遍历到的当前元素下标与之前处理过的那些元素下标 来计算和对比,是否满足 长度 k 这个条件。
上面三步分析已经非常透彻的分析了这类题目如何处理。比如有些题目会用到双指针滑动窗口, 实现思路都是完全一致,再结合题目条件的限制,来判断每一步是left指针滑动还是right指针滑动,然后再一边滑动一边处理left 与 right 所维护的长度为 [right - left + 1] 的窗口中的值。
具体实现,参见代码。对于每个位置下标的处理,我也做了透彻的分析和说明,为什么 有的 地方 +1 或者-1。只有搞懂了每一步的意义,那么这种题做一遍就能深深的刻在脑海中。
public int[] maxSlidingWindow(int[] nums, int k) {
int len = nums.length;
if(len == 0 || k == 0) return new int[]{};
int[] res = new int[len - k + 1];
LinkedList<Integer> list = new LinkedList<>();
int index = 0;
for(int i = 0; i < len; i++) {
//数组下标的每次移动就是滑动体现
while(!list.isEmpty() && nums[i] >= nums[list.peekLast()]) {
//滑的过程中要向题目要求靠拢
//如果当前遍历到的元素nums[i] 大于队列中最后一个值,则我们需要的是较大值,所以弹出较小值
//那如果不弹出 是不是就早就超过 k 的限制了
list.pollLast();
}
list.addLast(i);
//为什么这个条件就能判断队列中元素是否过期,因为数组小标总是比实际数组长度小
//这里的k即可以理解为数组长度,i 为下标,如果 能用 [下标 - 数组长度] 这个表达式就说明
//数组肯定是越界了,即某个元素该淘汰了
if(i - k == list.peekFirst()) {
list.pollFirst();
}
//始终记住一点 k 可以理解为 我们维护的一个固定的数组长度
//因为下标 始终小于 数组长度的, 因为要取 k 范围内的最小值,所k对应到下标中 要减1
if(i >= k - 1) {
res[index++] = nums[list.peekFirst()];
}
}
return res;
}