本篇带来两道经典的关于滑动窗口的算法题,有兴趣可在控制台跑一跑~
求和的最大值
题目来源:上一篇掘文《温故知新 —— Sliding Window》
比如给定如下数组:[ 5, 7, 1, 4, 3, 6, 2, 9, 2 ],求出 5 个连续元素的最大和是多少?
[5, 7, 1, 4, 3]
是第一组 5 个连续元素,求和是20
,[7, 1, 4, 3, 6]
是第二组 5 个连续元素,求和是21
......这样一直进行下去,最终对比发现 5 个连续元素的最大和是24
,由[4, 3, 6, 2, 9]
组成;
这里给出本瓜的另一种写法,仅供参考:
var maxSlidingWindow = function(nums, k) { const sum = function (arr) { return arr.reduce(function(prev, curr, idx, arr){ return prev + curr; }); }; let slidingWindow=nums.slice(0,k) let newSlidingWindow=[] let maxVal; for(let i=k;i<nums.length;i++){ newSlidingWindow=slidingWindow.slice(1).concat(nums[i]) maxVal=Math.max(sum(slidingWindow),sum(newSlidingWindow)) slidingWindow = newSlidingWindow } return maxVal }; const nums= [ 5, 7, 1, 4, 3, 6, 2, 9, 2 ] const k=5 maxSlidingWindow(nums,k) // 24
求最大值集合
题目来源:leetcode-239 (复杂)
给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。
返回滑动窗口中的最大值。
输入: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
本题在力扣中,难度被标为:复杂,这复杂吗?
- 写一个函数来判断数组中最大的数;
- 初始化窗口,求最大值保存;
- 滑动窗口,再求最大值保存;
- 滑动直至完毕;
本瓜题解:
/** * @param {number[]} nums * @param {number} k * @return {number[]} */ var maxSlidingWindow = function(nums, k) { if(k===1) return nums; // 特殊情况 let slidingWindow = nums.slice(0,k) let newSlidingWindow = [] let resArr = [] resArr.push(Math.max(...slidingWindow)) for(let i=k;i<nums.length;i++){ newSlidingWindow = slidingWindow.slice(1).concat(nums[i]) resArr.push(Math.max(...newSlidingWindow)) slidingWindow = newSlidingWindow } return resArr };
结果,当数组长度为 10 w 时,报错 超出时间限制
;
噢!用 Math.max()
来每次从窗口找最大值,时间复杂度是 O(n * k),仍然很大;
窗口固定,求最大值集合 在根本上是 单调队列 的问题!
本瓜录屏了一个非常棒的动画效果解释:
最后 JS 题解:
var maxSlidingWindow = function (nums, k) { // 队列数组(存放的是元素下标,为了取值方便) const q = []; // 结果数组 const ans = []; for (let i = 0; i < nums.length; i++) { // 若队列不为空,且当前元素大于等于队尾所存下标的元素,则弹出队尾 while (q.length && nums[i] >= nums[q[q.length - 1]]) { q.pop(); } // 入队当前元素下标 q.push(i); // 判断当前最大值(即队首元素)是否在窗口中,若不在便将其出队 while (q[0] <= i - k) { q.shift(); } // 当达到窗口大小时便开始向结果中添加数据 if (i >= k - 1) ans.push(nums[q[0]]); } return ans; };
实际上,滑动窗口还是有很多扩展的空间,即使是窗口滑动,怎么滑,滑动后怎么做,里面就存在很大的解题思路的差异!
以上。