理解:滑动窗口是双指针的一种应用,本质上是维护了一段数据区间,区间的左右端点移动是单调的。具体含义是:一个指针移动,另一个指针只能往一个方向移动,不能两个方向来回移动。 一般是去解决一些子数组,子串的问题的。
实质:通过发现题目的一些单调性质,对暴力循环的一种简化,时间复杂度从平方降到一次方。
实现:可以基于双端队列,也可以基于左右指针,优先考虑左右指针更节省空间,但是如果需要对窗口中的元素进行一定的处理操作,那么选择双端队列实现。左右指针是通过左指针代表左边界,右指针代表右边界,二者同时向右移动的基础原理。双端队列是左端点出数,右端点进数,不断的将元素入队和出队来实现的。
题目:
209. 长度最小的子数组
如果我们用暴力做法去做的话,我们需要去分别枚举子数组的左右端点。但我们可以发现很多子数组是没有必要去枚举的。例如:如果一个子数组的和都已经超过了tar了,我们还往后枚举,其实已经没有必要了。
我们可以观察到一个二段性质:子数组的和 ① >=tar ② code: 细节: 为什么用while不用if? while也可以起到if的作用,而且是连续性的if。如果只有if,可能会导致只删除了一个数以后,子数组的和还是满足条件。 713. 乘积小于 K 的子数组 跟上一题一样的思路,只是特判一下边界即可。 code: 细节: 固定了右端点以后,[l,r]有效的集合怎么算?[l-1,r]...[r,r]都是有效的集合,所以就是r-l+1 3. 无重复字符的最长子串 怎么记录每个字母出现的次数?用哈希表 code: 细节:这里用数组模拟哈希表了,原理是记录字符的ASCII值。 904. 水果成篮 也是用滑动窗口就可以解决了的问题。但这里用数组模拟就不太可行了,我们用unordered_map去记录每种水果出现的次数就行了。最后判断map.size()是否大于2即可。 code: 细节: unordered_map加入元素可以用mp[i]++,如果原来没有i这个key,会自动加上。删除元素如果只是mp[i]--,那么只会删除key对应的value的值,删除key要用到erase函数。 76. 最小覆盖子串 这个题目需要记录的窗口中的子串里面需要有t串中所有的元素,其实种类数量都需要对应上。这不是正好可以用哈希表去解决吗 我们可以开两个哈希表,一个是s中出现的元素种类与个数,另一个记录t中出现的种类与个数。 另外还得开一个cnt变量,去记录当前窗口中满足t串元素的个数。因为如果当前s中只有t串元素的子集,那是不能更新答案的。 右端点前进,元素放入s哈希表中,如果遇到了t中的元素,这个时候我们需要增加cnt。因为这是第一次遇见,所以这个元素一定是需要的。 同时我们需要考虑左端点的值,如果左端点的对应的值已经超过t中某元素的数量上限,我们需要剔除该元素并前移一位。 当cnt == t.size()的时候,说明我们可以收集答案了。答案就是ans与当前窗口长度的最小值。 code: 3624. 三值字符串 - AcWing题库 也是一道模板题。关键是记录元素的种类已经个数。当元素个数到达3个的时候开始考虑移动左端点。 code: 187. 重复的DNA序列 按题意直接模拟一个窗口。 code: 219. 存在重复元素 II 感觉跟滑动窗口很像,但本质就是用哈希记录每个端点出现的下标。 code: insert是为了方便处理,下标从1开始。 395. 至少有 K 个重复字符的最长子串 力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台 这题很特殊,由于我们不知道区间可能会出现几种字母,所以我们很难用双指针。但是考虑到一共就26个字母,所以我们直接26次暴力枚举一下,然后每种情况滑动窗口一下就行了 当遇到新元素,diff_cnt++,否则就不是新元素,那该元素的cnt++;当我们遇到的元素种类大于i的时候,开始缩小窗口。 code: 438. 找到字符串中所有字母异位词 这里要维护的集合需要额外的两个变量,一个是需要的字符数量cnt,另一个是目前多余的字符数量diff_cnt。用两个哈希表去记录,一个记录字符串p中出现的元素,另一个记录当前窗口中出现的元素次数。 当窗口右移的时候,哈希表加入当前的字符,先判断是否这个字符是否是需要的字符,如果满足maps[s[j]] <= mapp[s[j]],说明是需要这个字符的,如果不满足这个条件,有两个可能:第一:需要字符的种类已经超出需要的数目了,第二:出现了不需要字符的种类。这个时候diff_cnt就要加了。 左指针移动的条件是:当左指针指向的字符的数目已经大于需要的字符数目的时候,就要移动了。移动的时候要记得删除cnt与diff_cnt的数目。 收集结果:必须满足cnt == p.size()且diff_cnt == 0,说明当前维护的区间里面的值没有多余的字符。 2799. 统计完全子数组的数目 关键点就是当我们找到满足的数组以后,包括该数组的左边数组其实都是满足要求的! 所以我们移动左端点的原则就是让当前区间是最小的合法区间。那必须是要满足两个条件:第一:区间里面数的种类达到k个,第二就是每个数的数量必须为1。所以是mp.size() == k && mp[nums[i]]>1。 统计结果的时候我们也需要满足mp.size() == k。 2260. 必须拿起的最小连续卡牌数 这个比较模板,只需要记录一下最小的答案就行了。判断条件就是右指针指向的数出现了两次,那就可以缩小区间了,最后那个区间长度就是答案。 1234. 替换子串得到平衡字符串 反向思维:先记录所有字符出现的次数。 题目都很难读懂。 意思就是把分成两个区间,一个是可替换区间,这个以外的是不可替换区间。只有当不可替换区间的每种字母数目不超过m/4才可以用可替换区间里面的字符去变化。所以我们左端点移动的条件就是每种字母数目不超过m/4。 力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台 1456. 定长子串中元音的最大数目 模拟一个滑动窗口就可以了,哈希表记录窗口里面的元音的次数。 1658. 将 x 减到 0 的最小操作数 逆向思维:只能从左或者右减去,所以最后的答案必然是原数组的一个子数组。所以问题转换成找到原数组的一个子数组的和为x,求子数组最大长度。 accmulate:(pos,pos,x)x为初始累加值。 2762. 不间断子数组 分析完题目其实就是记录三个值:集合中的最大值,最小值,跟即将加进来的数。 我们可以用multiset(红黑树)去维护。 每次加入multiset中,由于是自动排序的,最大值就是st.rbegin(),最小值是st.begin()。如果当前这个数的加入让最大值与最小值之差小于2了,那么我们就要开始移动左指针了。直到满足条件即可。 最后的答案就是区间长度。 code: 注意: 最后一个元素的指针不是end(),是rbegin()。 erase可以接受值或者是指针。如果接受的是指针,删除的是该指针指向的值,如果是值,那multise中所有该值都会被删除。 与单调栈,单调队列的区别:这里的区间内元素顺序不是固定的,所以我们可以用multiset,如果元素顺序固定就要用单调队列了。 一些关于哈希表的补充: class Solution {
public:
int minSubArrayLen(int target, vector
力扣 713: 乘积小于k的子数组
class Solution {
public:
int numSubarrayProductLessThanK(vector
力扣3 无重复字符的最长子串:
class Solution {
public:
int map[1000];
int lengthOfLongestSubstring(string s) {
int n = s.size();
int ans = 0;
for(int i = 0,j = 0;j
力扣904: 水果成篮
class Solution {
public:
int totalFruit(vector
力扣76: 最小覆盖子串
class Solution {
public:
int cnt;
string minWindow(string s, string t) {
unordered_map
acwing 3624 三值字符串
#include
力扣187:重复的DNA序列
class Solution {
public:
vector
力扣219:存在重复元素Ⅱ
class Solution {
public:
unordered_map
力扣395:至少有k个重复字符的最长子串
class Solution {
public:
int map[26];
int longestSubstring(string s, int k) {
int ans = 0;
for(int i = 1;i<=26;i++)
{
memset(map,0,sizeof (map));
int diff_cnt = 0,cnt = 0;
for(int l = 0, r = 0;r
力扣438,LCR015: 找到字符串中的所有字母异位词
class Solution {
public:
vector
力扣930,2799:和相同的二元子数组 统计完全子数组的数目
class Solution {
public:
// 左边的数都是满足要求的!!!
int countCompleteSubarrays(vector
力扣2260: 必须拿起的最小连续卡牌数
class Solution {
public:
int map[1000005];
int minimumCardPickup(vector
力扣1234: 替换子串得到平衡字符串
class Solution {
public:
int map[1000];
int balancedString(string s) {
int n = s.size(), m = n/4;
for(auto i: s) map[i]++;
int ans = n;
if(map['Q'] == m && map['W'] == m && map['E'] == m && map['R'] == m) return 0;
for(int i = 0,j = 0;j
力扣1456:定长子串中的元音的最大数目
class Solution {
public:
int map[1000];
int maxVowels(string s, int k) {
int n = s.size();
if(n
力扣1658:将x减到0的最小操作数
class Solution {
public:
int minOperations(vector
力扣2762:不间断子数组
class Solution {
public:
long long continuousSubarrays(vector