【LeetCode 笔记】滑动窗口

滑动窗口就像是一个爬虫,在一个序列中进行遍历,头部扩容进入窗口,在尾部舍弃,在此刻进行条件判断。

写法一(from labuladong 的算法小抄):

/* 滑动窗口算法框架 */
void slidingWindow(string s, string t) {
    unordered_map<char, int> need, window;
    for (char c : t) need[c]++;
    
    int left = 0, right = 0;
    int valid = 0; 
    while (right < s.size()) {
        // c 是将移入窗口的字符
        char c = s[right];
        // 右移(增大)窗口
        right++;
        // 进行窗口内数据的一系列更新
        ...

        /*** debug 输出的位置 ***/
        printf("window: [%d, %d)\n", left, right);
        /********************/
        
        // 判断左侧窗口是否要收缩
        while (window needs shrink) {
            // d 是将移出窗口的字符
            char d = s[left];
            // 左移(缩小)窗口
            left++;
            // 进行窗口内数据的一系列更新
            ...
        }
    }
}

// 或者

int slidingWindow(string s) {
    unordered_map<char, int> window;

    int left = 0, right = 0;
    int res = 0; // 记录结果
    while (right < s.size()) {
        // c 是将移入窗口的字符
        char c = s[right];
        // 右移(增大)窗口
        right++;
        // 进行窗口内数据的一系列更新
        ...
        
        // 判断左侧窗口是否要收缩
        while (window needs shrink) {
            // d 是将移出窗口的字符
            char d = s[left];
            // 左移(缩小)窗口
            left++;
            // 进行窗口内数据的一系列更新
            ...
        }
        // 在这里更新答案
        // 例如 res = max(res, right - left);
    }
    // return res;
}

注意,该模板为左闭右开区间 [left, right),因此在遍历的时候,窗口只到达 right - 1 的范围,窗口大小为 right - left

写法二(from 负雪明烛):

int slidingWindow(vector<int>& nums) {
	int left = 0, right = 0;
	int sums = 0;	// 用于统计 子数组/子区间 是否有效,根据题目可能会改成求和/计数
	int res = 0;	// 保存最大的满足题目要求的 子数组/子串 长度
	while (right < nums.size()) {
		sums += nums[right]; # 增加当前右边指针的数字/字符的求和/计数
        while (区间[left, right]不符合题意) {
        	// 此时需要一直移动左指针,直至找到一个符合题意的区间
        	sums -= nums[left]; // 移动左指针前需要从counter中减少left位置字符的求和/计数
            left++; 		// 真正的移动左指针,注意不能跟上面一行代码写反
        }
        // 到 while 结束时,我们找到了一个符合题意要求的 子数组/子串
        res = max(res, right - left + 1); // 需要更新结果
        right++;	 	// 移动右指针,去探索新的区间
	}
	return res;
}

1004. 最大连续1的个数 III

题目描述

在这里插入图片描述

题解

分享滑动窗口模板,秒杀滑动窗口问题 - 负雪明烛

题目可转化为:在最多变换 k 次的条件下,找出最长连续子序列。

左闭右开区间写法:

class Solution {
public:
    int longestOnes(vector<int>& nums, int k) {
        int left = 0, right = 0, res = 0, zeros = 0;
        while (right < nums.size()) {
            right++;
            if (nums[right - 1] == 0) {
                zeros++;
            }
            while (zeros > k) {
                if (nums[left++] == 0) {
                    zeros--;
                }
            }
            res = max(res, right - left);
        }
        return res;
    }
};

闭区间写法:

class Solution {
public:
    int longestOnes(vector<int>& nums, int k) {
        int left = 0, right = 0, res = 0, zeros = 0;
        for (right = 0; right < nums.size(); right++) {
            if (nums[right] == 0)   zeros++;
            while (zeros > k) {
                if (nums[left++] == 0)  zeros--;
            }
            res = max(res, right - left + 1);
        }
        return res;
    }
};

2024. 考试的最大困扰度

题目描述

在这里插入图片描述在这里插入图片描述

题解

这道题和 1004. 最大连续1的个数 III 的思路一模一样,都可以转化为 “在给定条件下,求最长连续子序列”,该题增加的内容无非就是考虑两种情况:

  • 将 ‘T’ 变成 ‘F’
  • 将 ‘F’ 变成 ‘T’

然后综合选取二者中的较大值。

class Solution {
public:
    int maxConsecutive(string answerKey, int k, char ch) {
        int left = 0, right = 0, res = 0, flags = 0;
        for (right = 0; right < answerKey.length(); right++) {
            if (answerKey[right] == ch)    flags++;
            while (flags > k) {
                if (answerKey[left++] == ch) {
                    flags--;
                }
            }
            res = max(res, right - left + 1);
        }
        return res;
    }

    int maxConsecutiveAnswers(string answerKey, int k) {
        return max(maxConsecutive(answerKey, k, 'T'), maxConsecutive(answerKey, k, 'F'));
    }
};

904. 水果成篮

题目描述

在这里插入图片描述
在这里插入图片描述

题解

该题看着题目很长,理解下来转换后,就变成 “只有两种数字的最长连续子序列”

class Solution {
public:
    int totalFruit(vector<int>& fruits) {
        int left = 0, right = 0, res = 0, kinds = 0;
        unordered_map<int, int> map;
        while (right < fruits.size()) {
            int c = fruits[right++];
            if (map[c] == 0) {
                ++kinds;
            }
            map[c]++;
            
            // 当窗口内数字种类大于二,考虑缩小窗口
            while (kinds > 2 && left < right) {
                int d = fruits[left++];
                if (map[d] != 0) {
                    map[d]--;
                    if (map[d] == 0) {
                        --kinds;
                    }
                }
            }
            res = max(res, right - left);
        }
        return res;
    }
};

1695. 删除子数组的最大得分

题目描述

在这里插入图片描述

题解

这道题与 904. 水果成篮 可以说是一模一样

问题转化为“求不含重复元素的连续子数组最大和”,思路还是利用滑动窗口记录数组区间,当遇到重复元素就缩小窗口,直到窗口内不含有重复元素。(元素个数的记录可利用Map)

class Solution {
public:
    int maximumUniqueSubarray(vector<int>& nums) {
        int left = 0, right = 0, res = 0, sum = 0;
        unordered_map<int, int> map;
        while (right < nums.size()) {
            int c = nums[right++];
            sum += c;
            map[c]++;
            
            while (left < right && map[c] > 1) {
                int d = nums[left++];
                map[d]--;
                sum -= d;
            }
            res = max(res, sum);
        }
        return res;
    }
};

713. 乘积小于 K 的子数组

题目描述

题目描述

题解

这道题出现了 “小于k” 和 “连续子数组”,立马想到滑动窗口

class Solution {
public:
    int numSubarrayProductLessThanK(vector<int>& nums, int k) {
        if(k == 0 || k == 1) return 0;
        int left = 0, right = 0, ans = 0, prod = 1;
        while (right < nums.size()) {
            prod *= nums[right];
            while (left <= right && prod >= k) {
                prod /= nums[left++];
            }
            // ans加上从[left, right]区间中的不重复连续子数组个数
            /**
                举个例子 [...5,6,3,4,8,...] 假设r遍历到了数值8的位置, l经过摘除后移到了数值5处, 
                此时所有的组合情况是:
                [56348]
                [6348]
                [348]
                [48]
                [8]
                即5种
            */
            ans += right - left + 1;
            
            right++;
        }
        return ans;
    }
};

Note

滑动窗口是动态规划的一种具体用法,当题目有具体限制要求的时候,比如求和为k的最大子数组的长度,滑动窗口不失为一种可考虑的方法。

滑动窗口不能使用的情况:

  • 没有更多的限制条件能使得窗口收缩
  • 某些求 最大值 而不是求 最长 连续子数组的情况(如 53. 最大子数组和),不能把求最大值转化为求最长

此时需要考虑使用DP之外或者DP类的其他方法(如前缀和)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Beta Lemon

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值