前言
leetcode链接:
实现声明~~(叠甲)~~,我只是个小菜鸡,leetcode总共就完成了100来题,大多数是简单的那种,遇到这题是因为我最近在刷滑动窗口的题目,我以为很简单,结果光速白给,大家看个乐子。
题目详情
给你一个整数数组 nums
,有一个大小为 k
的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k
个数字。滑动窗口每次只向右移动一位。
返回 滑动窗口中的最大值 。
示例 1:
ini
输入: nums = [1,3,-1,-3,5,3,6,7], k = 3
输出: [3,3,5,5,6,7]
解释:
滑动窗口的位置 最大值
--------------- -----
[1 3 -1] -3 5 3 6 7 3
1 [3 -1 -3] 5 3 6 7 3
1 3 [-1 -3 5] 3 6 7 5
1 3 -1 [-3 5 3] 6 7 5
1 3 -1 -3 [5 3 6] 7 6
1 3 -1 -3 5 [3 6 7] 7
示例 2:
ini
输入: nums = [1], k = 1
输出: [1]
白给暴力解法
第一想法是困难题就这?不就是左指针(i)遍历nums,然后右指针从(i -> i+k)编辑nums,记录每次遍历的最大值存起来,直接解决。
ts
function maxSlidingWindow(nums: number[], k: number): number[] {
let ans:number[] = [];
for(let left = 0; left < nums.length - k + 1; left++) {
let right = left;
let tempAns = -Infinity;
while(right < left + k) {
tempAns = Math.max(tempAns, nums[right]);
right++;
}
ans.push(tempAns);
}
return ans;
};
然后就直接白给了
失败总结
算了下,用例是长度为64317
的数组,滑动窗口的长度是32879
。
在这段代码中,外层循环运行次数为 nums.length - k + 1
次,内层循环运行次数为 k
次。因此,总的运行次数为 (nums.length - k + 1) * k
次,那复杂度约为为O(n * k)
。
怪不得会超时,这段代码得优化到至少 O(n)
才能通过。
思考过程
1、如果要优化到 O(n)
,那么里面的那层k次循环的必须优化成 O(1)
。
2、那么我们就要思考这层内循环的意思是什么,这层内循环是为了获取滑动窗口内的最大值。
分类讨论
但是我们是否真的需要循环去遍历得到最大值?其实是不需要的,因为这里面有个规律,我们可以从示例中得到。
ini
输入: nums = [1,3,-1,-3,5,3,6,7], k = 3
输出: [3,3,5,5,6,7]
解释:
滑动窗口的位置 最大值
--------------- -----
[1 3 -1] -3 5 3 6 7 3
1 [3 -1 -3] 5 3 6 7 3
1 3 [-1 -3 5] 3 6 7 5
1 3 -1 [-3 5 3] 6 7 5
1 3 -1 -3 [5 3 6] 7 6
1 3 -1 -3 5 [3 6 7] 7
假设我们滑动窗口的边界坐标是[i, j]
,会获得一个最大值,我们记为 r0
然后接着开始滑动,窗口的边界坐标是[i+1, j+1]
,记移动后新出现的数是 n1
,那么会有以下情况:
1、r0
> n1
,且r0
对应的坐标处于[i+1, j+1]
,那么此时的最大值r1 = r0
;
2、r0
> n1
,且r0
不处于[i+1, j+1]
,那么此时最大值为[i,j]之中非r0的数值
和n1
之间产生;
3、r0
<= n1
,那么此时的最大值r1 = n1
;
构建递减数组:
1、3这种情况都一目明了,只有2需要思考一下怎么处理。
2这种情况的话,是一个数组中在O(1)时间内找到最大值,那么自然想到,如果这个数组是递减或递增的
,那么从 0
或者 length-1
这两个位置就能拿到最大值。
那么如何构建这个这个玩意呢,假设我们构建的是 递减的
数组,那么我们需要做的事情就很明确:
每次push前,判断被push的元素是否比数组内的元素大,有的话,就把那些元素给踢出去。
那么,你就能得到一个递减的数组。
单调队列解法
ts
function maxSlidingWindow(nums: number[], k: number): number[] {
const result: number[] = [];
// 存储坐标的单调队列
// 为什么存坐标,是因为我们还要记录坐标是否越界了,就存nums[i]无法知道是否越界
const queue:number[] = [];
// i可以理解为右指针
for(let i = 0; i < nums.length; i++) {
// 判断被push的元素是否比数组内的元素大,有的话,就把那些元素给踢出去
// 这边从末尾踢是因为递减的,末尾元素一定是最小的,
// 从小开始比较可以一直用queue.length-1作为比较坐标,不需要额外的变量去记录
while (queue.length > 0 && nums[i] >= nums[queue[queue.length-1]]) {
queue.pop();
}
queue.push(i);
// 如果移动后最大值越界了,所以要把最大值踢出去
if (i - queue[0] >= k) {
queue.shift();
}
// 记录答案,右指针要大于等于k-1的时候才形成宽度为k的窗口
if (i >= k-1) {
// 队首就是最大值
result.push(nums[queue[0]]);
}
}
return result;
};
提交结果
结语
做了几道滑动窗口的题目后,有那么一点点感觉,但是又没有完全掌握,属实折磨,大概这就是之前一直不乐意刷leetcode的原因罢~~(自食其果)~~。
相关题目链接: