「这是我参与2022首次更文挑战的第3天,活动详情查看:2022首次更文挑战」。
介绍
滑动窗口算法,与 双指针算法 类似,也是需要两个指针,但是不同点就是,双指针算法的注重点是两个指针所在的位置上的元素的值,而滑动窗口算法则是两个指针之间的序列值所组成的(窗口)。
滑动窗口,依名知意。首先窗口不是固定位置的,而是动态的移动。而且窗口的大小不一定是固定的,而是跟随两个指针的不同位置,窗口的大小来进行动态改变
1.应用场景
滑动窗口算法的应用场景 一般出现在 数组 或者 链表
2.滑动窗口算法分类
滑动窗口算法分类有两种,一种是 固定窗口,另一种是 滑动窗口
-
固定窗口
固定窗口 看似是需要两个指针,但是,由于窗口大小的固定,我们可以根据右指针的位置,就很容易的获取到左指针的位置,通过
left = right - k。使用场景: -
滑动窗口 滑动窗口 则需要左右两个指针,两个指针的位置是根据相关条件的不同,中途会动态改变,进而导致窗口的大小也会动态的改变。应用场景:
题目
-
子数组最大平均数-643
给你一个由
n个元素组成的整数数组nums和一个整数k。请你找出平均数最大且 长度为k的连续子数组,并输出该最大平均数 -
爱生气的书店老板-1052
今天,书店老板有一家店打算试营业 customers.length 分钟。每分钟都有一些顾客(customers[i])会进入书店,所有这些顾客都会在那一分钟结束后离开。在某些时候,书店老板会生气。 如果书店老板在第 i 分钟生气,那么 grumpy[i] = 1,否则 grumpy[i] = 0。 当书店老板生气时,那一分钟的顾客就会不满意,不生气则他们是满意的。书店老板知道一个秘密技巧,能抑制自己的情绪,可以让自己连续 X 分钟不生气,但却只能使用一次。请你返回这一天营业下来,最多有多少客户能够感到满意。
-
替换后的最长重复字符-424
给你一个仅由大写英文字母组成的字符串,你可以将任意位置上的字符替换成另外的字符,总共可最多替换 k 次。在执行上述操作后,找到包含重复字母的最长子串的长度
-
最小覆盖子串-76
给你一个字符串
s、一个字符串t。返回s中涵盖t所有字符的最小子串。如果s中不存在涵盖t所有字符的子串,则返回空字符串""
代码
-
固定窗口
-
子数组最大平均数 I 643题
const findMaxAverage = (nums, k) => { let sum = 0 let maxSum = 0 const numsLength = nums.length for (let i = 0; i < numsLength; i++) { // 第一段滑动窗口 [0, k-1] if (i < k) { sum += nums[i] if (i === k - 1) maxSum = sum } else { // 当 i = k 时,滑动窗口开始第一次右移,[i - k + 1, i] 这段就是滑动窗口 sum = sum - nums[i - k] + nums[i] if (sum > maxSum) maxSum = sum } } return maxSum / k } -
爱生气的书店老板 1052题
const maxSatisfied = (customers, grumpy, minutes) => { // 老板不生气时的所有满意顾客 let got = 0 // 老板控制情绪时滑动窗口中增加的满意顾客,也就是滑动窗口中原本不满意的顾客 let total = 0 // 在滑动窗口移动过程中 total 的最大值 let max = 0 for (let i = 0; i < customers.length; i++) { if (!grumpy[i]) got += customers[i] // 判断是否是第一段滑动窗口 [0, minutes -1] if (i < minutes) { // 判断老板是否处于生气状态 if (grumpy[i]) total += customers[i] } else { // 当 i = minutes 时,滑动窗口开始第一次右移 // 滑动窗口进行右移,[i - minutes + 1, i] 这段就是滑动窗口 // 因为右移,所以判断左侧离开滑动窗口的 i - minutes ,是否是老板处于生气状态 if (grumpy[i - minutes]) total -= customers[i - minutes] // 因为右移,所以判断右侧进入滑动窗口的 i ,是否是老板处于生气状态 if (grumpy[i]) total += customers[i] } if (total > max) max = total } // 原本的满意顾客 + 情绪控制区最多能收揽的不满意顾客 return got + max }
-
-
滑动窗口
-
替换后的最长重复字符 424题 *
const characterReplacement = (s, k) => { let n = s.length let left = 0 let right = 0 let maxNum = 0 let strMap = new Array(26).fill(0) const getIndex = (str) => str.charCodeAt() - 'A'.charCodeAt() while (right < n) { strMap[getIndex(s[right])]++ maxNum = Math.max(maxNum, strMap[getIndex(s[right])]) // 窗口宽度 > 最长子串 if (right - left + 1 > maxNum + k) { // 窗口平移 strMap[getIndex(s[left])]-- left++ } right++ } return n - left } -
最小覆盖子串 76题 *
const minWindow = (s, t) => { // 先制定目标 根据t字符串统计出每个字符应该出现的个数 let targetMap = makeCountMap(t) let sl = s.length let tl = t.length let left = 0 // 左边界 let right = -1 // 右边界 let countMap = {} // 当前窗口子串中 每个字符出现的次数 let min = '' // 当前计算出的最小子串 // 循环终止条件是两者有一者超出边界 while (left <= sl - tl && right <= sl) { // 和 targetMap 对比出现次数 确定是否满足条件 let isValid = true Object.keys(targetMap).forEach((key) => { let targetCount = targetMap[key] let count = countMap[key] if (!count || count < targetCount) { isValid = false } }) if (isValid) { // 如果满足 记录当前的子串 并且左边界右移 let currentValidLength = right - left + 1 if (currentValidLength < min.length || min === '') { min = s.substring(left, right + 1) } // 也要把map里对应的项去掉 countMap[s[left]]-- left++ } else { // 否则右边界右移 addCountToMap(countMap, s[right + 1]) right++ } } return min } const addCountToMap = (map, str) => { if (!map[str]) { map[str] = 1 } else { map[str]++ } } const makeCountMap = (strs) => { let map = {} for (let i = 0; i < strs.length; i++) { let letter = strs[i] addCountToMap(map, letter) } return map }
-
总结
滑动窗口算法 作为和 双指针算法 相似的算法,也是数据结构中 基础算法之一。它也是两个指针起到关键作用,但是由于作用位置和区域是不同的,所以两者进行区分。