往往
往往
Published on 2025-05-27 / 1 Visits
0
0

【算法题单】滑动窗口

提单链接:https://leetcode.cn/discuss/post/3578981/ti-dan-hua-dong-chuang-kou-ding-chang-bu-rzz7/

一、定长滑动窗口

对于此类问题,可以归类为同种方法:

即在窗口右侧插入元素,达到窗口给定长度,进行相应操作,推出末尾元素,进行相应操作,继续循环

注意:因为一定要插入和推出元素,所以窗口大小必须大于0,如果窗口大小可能为0,则需要特殊处理

let ans = 0 
let count = 0
for(let i = 0;i < arr.length ;i++){ //  i 指向当前想要插入窗口的元素
  if(k == 0){
    return 变量
  } 
  count += arr[i]                      //  插入后操作,根据题目变化
  if(i< k - 1){
    continue                           // 如果窗口长度还未达到指定宽度 - 1,不进行元素退出操作直接开启下一次循环
  }                                        //(为何是k - 1 ? 因为i指向的是想要插入窗口的元素,所以i = k -1 时,是第一次定长窗口
                                           //   完整的时刻)
  ans = Math.max(ans,count)            // 计算结果,根据题目变化
  count -= arr[i - k + 1]              //末尾元素退出,给下一个插入的元素让位
}

LeetCode例题:

1343. 大小为 K 且平均值大于等于阈值的子数组数目

1423. 可获得的最大点数 

1652. 拆炸弹

二、不定长滑动窗口

1.求最长/最大

此类题型一般使用思路:

1.逐渐扩展(逐步增大窗口右端点即窗口增大,尽可能包裹更多元素)

2.达到条件(根据题目不同,窗口增大导致达成题干条件,此时右端点因达成条件,无法移动,需要等待左端点操作)

3.逐渐缩小(逐渐增大窗口左端点即减小窗口,直至不再违反条件,而后重复步骤1

注意:此类题型因为要逐渐缩小窗口,所以有记忆先前元素的需求,一般配合哈希表Map使用

let maxlen = 0
    const map = new Map()
    let left = 0
    for (let right = 0; right < nums.length; right++) {
        map.set(nums[right],(map.get(nums[right] )|| 0) + 1)
        while(map.get(nums[right]) > k){
            map.set(nums[left],map.get(nums[left]) - 1)
            left++
        }
        maxlen = Math.max(maxlen,right - left + 1)
    }
    return maxlen

LeetCode例题:

3090. 每个字符最多出现两次的最长子字符串

904. 水果成篮

2958. 最多 K 个重复元素的最长子数组 

2.求最短/最小

解题思路同“求最长/最大”,其中的Math.max替换为Math.min

注意:关注Infinity的问题,无穷不能作为输出值,所以需要额外判断

let ans = Infinity
    let count = 0
    let l = 0
    for(let r = 0;r < nums.length;r++){
        count += nums[r]
        while(count >= target){
            ans = Math.min(ans,r - l + 1)
            count -= nums[l]
            l++
        }
    }
    if(ans == Infinity){
        return 0
    }else{
        return ans
    }

LeetCode例题:

2904. 最短且字典序最小的美丽子字符串

1234. 替换子串得到平衡字符串

3.恰好型滑动窗口

例如,要计算有多少个元素和恰好等于 k 的子数组,可以把问题变成:

计算有多少个元素和 ≥k 的子数组。

计算有多少个元素和 >k,也就是 ≥k+1 的子数组。

答案就是元素和 ≥k 的子数组个数,减去元素和 ≥k+1 的子数组个数。这里把 > 转换成 ≥,从而可以把滑窗逻辑封装成一个函数 f,然后用 f(k) - f(k + 1) 计算,无需编写两份滑窗代码。

总结:「恰好」可以拆分成两个「至少」,也就是两个「越长越合法」的滑窗问题。

注:也可以把问题变成 ≤k 减去 ≤k−1(两个至多)。可根据题目选择合适的变形方式。

注:也可以把两个滑动窗口合并起来,维护同一个右端点 right 和两个左端点 left ,构成三指针滑动窗口

思路代码如下:

const n = nums.length;
    let left1 = 0, left2 = 0, right = 0;     //维护两个左指针和一个公共右指针,每次右指针循环时得出的左指针,相减即为子数组/子字符串数量
    let sum1 = 0, sum2 = 0;                  //记录两个窗口各自的和,以匹配条件
    let ret = 0;
    while (right < n) {
        sum1 += nums[right];
        while (left1 <= right && sum1 > goal) {
            sum1 -= nums[left1];
            left1++;
        }
        sum2 += nums[right];
        while (left2 <= right && sum2 >= goal) {
            sum2 -= nums[left2];
            left2++;
        }
        ret += left2 - left1;
        right++;
    }
    return ret;

LeetCode例题:

930. 和相同的二元子数组

1248. 统计「优美子数组」


Comment