本篇主要总结关于滑动窗口的相关做题技巧与想关例题。理论部分不会涉及。
1.定义窗口变量——窗口需要维护的变量:数之和,最大最小长度,满足一定条件的元素个数
2.定义窗口,开始滑动——定义左右边界初始化为0,在右边界小于数组长度条件下滑动
3.扩大窗口(合法更新)——添加右边界,满足条件时更新需要维护的变量
4.收缩窗口(非法更新)—— 在滑动窗口无效或者即将无效的情况下,更新维护的变量,并且收缩滑动窗口的左边界,
如果是求子串(子数组)最短长度,在符合条件时更新维护变量和结果
如果是求子串(子数组)满足一定条件的最长子串,在收缩窗口后(刚好满足时)更新
二次更新的两种情况:
固定
的!!! 使用 if条件
来更新可变
的!!! 使用 while条件
来更新5.返回得到答案
代码模板
int func(vector& nums,int k)
{
//Step1. 定义维护变量:
1. unordered_map m; //在需要统计字符或者数字出现的次数的时候,使用哈希表
2. int sum=0,res=0; //在需要记录整数数组中的子序列和或者其他求和时,使用sum记录每一次滑动窗口的子和,再利用res得到最大的或者最小的结果
3. int len=0,start=0; //得到字符串的字串,len记录字串长度,start标识字串开始位置
//Step2. 确定滑动窗口的边界,开始滑动:
int left=0,right=0;
while (right< n) // n: 数组长度
{
//Step3. 合法更新:每进行一次滑动时,必须要更新的变量:如Step1的哈希表,sum,res,len与start等等
......
if (满足条件) //满足某一种条件:例如滑动窗口的长度:right-left+1 与某个值相等时,则进行一次判断,保存临时结果
{
//更新:res=max(res,sum) ......
}
//Step4: 非法更新
//(1): 滑动窗口的长度不固定:使用while来更新窗口
while (right-left+1 > m.size())//举个例子:无重复字符的最长字串,你的窗口长度大于哈希表的长度,则一定含有重复元素,因此更新左边界,使用while
{
.....
}
//(2): 滑动窗口的长度固定,使用if来更新窗口
if (right>=k-1)//举个例子:子数组的最大平均值,子数组规定长度不能超过k,因此滑窗长度固定
{
.....
}
// 求最大长度,最大数目,刚好满足时在此更新结果
right++;//此处可以改为for循环
}
return res;//Step5: 返回结果
}
3.无重复字符的最长子串
Step4: 滑动窗口不具有固定长度,所以使用while循环来更新滑动窗口
class Solution {
public:
int lengthOfLongestSubstring(string s) {
//1. 确定维护变量:存储字符个数的哈希表
unordered_map m;
int maxLen = 0;
//2. 定义滑动窗口的边界,开始滑动窗口
int n = s.size();
int left = 0, right = 0;
while (right < n)
{
//3. 合法更新:哈希表存储字符的更新,字符放到哈希表中
m[s[right]]++;
//4. 非法更新:窗口的长度大于哈希表的长度
//窗口里有重复的单词,非法了,需要更新窗口的左边界,必要时删除此字符,删除时边长不增,不需要更新变量maxLen
while (right - left + 1 > m.size())
{
m[s[left]]--;
if (m[s[left]] == 0) {
m.erase(s[left]);
}
left++;
}
// 收缩窗口后刚好无重复更新结果
if (right-left+1 == m.size()) {// 区间长度等于元素个数,说明不重复,符合条件
maxLen=max(maxLen, right-left+1);
}
right++;
}
//5. 返回结果
return maxLen;
}
};
643子数组最大平均数 I
Step4:滑动窗口的长度固定:为k ,使用 if条件来更新滑动窗口
class Solution {
public:
double findMaxAverage(vector& nums, int k) {
//1. 定义维护变量
int sum = 0;
int res = INT_MIN;
//2. 确定滑动窗口的左右边界,开始滑动窗口
int left = 0, right = 0;
int n = nums.size();
while (right < n)
{
//3. 合法更新:滑动每一步,只要滑动的长度等于k,则记录一下此时的sum值
sum += nums[right];
if (right - left + 1 == k)
{
res = max(res,sum);
}
//4. 非法更新:滑动窗口的长度固定,使用if来更新,
if (right > k-1)
{
sum -= nums[left];
left++;
}
// 收缩窗口后刚好长度为k
if (right - left + 1 == k) {
res = max(res,sum);
}
right++;
}
return static_cast(res) / k;
}
};
159.至多包含两个不同字符的最长子串
//至多包含两个不同字符的最长字串
class Solution_159 {
public:
int lengthOfLongestSubstringTwoDistinct(string s) {
//1. 定义维护变量
unordered_map m;
int maxLen = 0;
//2. 确定窗口的左右边界,开始滑动窗口
int left = 0, right = 0;
int n = s.size();
while (right < n)
{
//3. 合法更新:滑动过程中map存储每一个字符
m[s[right]]++;
//4. 非法更新:最多存储两个不同的字符,但是对于每种字符的存储数量没有限制,因此可以存储很多很多,但是不能超过两种,所以滑动窗口的长度不固定 使用while来更新滑动窗口
// 哈希表的长度大于3,则至少出现了3种不同的字符,因此窗口不合法, 需要更新
while (m.size() > 2) // right >= k-1
{
m[s[left]]--;
if (m[s[left]] == 0)
{
m.erase(s[left]);
}
left++; // 收缩窗口
}
if (m.size() <= 2)
{
maxLen = max(maxLen, right - left + 1);
}
right++;
}
return len;
}
};
209长度最小的子数组
class Solution {
public:
int minSubArrayLen(int target, vector& nums) {
int sum = 0;
int maxLen = INT_MAX;
int left = 0, right = 0;
int n = nums.size();
while (right=target
while (sum >= target)
{
maxLen = min(maxLen, right - left+1);
sum -= nums[left];
left++;
}
right++;
}
return (maxLen == INT_MAX) ? 0 : maxLen;
}
};
1695删除子数组的最大得分
实际就是无重复子数组的最大元素之和
class Solution {
public:
int maximumUniqueSubarray(vector& nums) {
unordered_map m;
int sum = 0;
int res = 0;
int left = 0, right = 0;
int n = nums.size();
while (right m.size())
{
sum -= nums[left];
m[nums[left]]--;
if (m[nums[left]] == 0)
{
m.erase(nums[left]);
}
left++;
}
if (right-left+1 == m.size())
{
res=max(res,sum);
}
right++;
}
return res;
}
};
438. 找到字符串中所有字母异位词
class Solution {
private:
unordered_map m_s;
unordered_map m_p;
public:
vector findAnagrams(string s, string p) {
for (auto& x:p)
{
m_p[x]++;
}
int left=0,right=0;
int n1=s.size();
int n2=p.size();
vector vec;
while (right < n1)
{
//3. 合法更新: 窗口滑动,字符存储入哈希表
m_s[s[right]]++;
//4. 非法更新:滑动窗口有固定长度,长度为p的长度,因此使用if条件来维护
if (right > n2 - 1)
{
m_s[s[left]]--;
if (m_s[s[left]]==0)
{
m_s.erase(s[left]);
}
left++;
}
//当哈希表的元素存储和目标p完全一致。则left起始下标就是一个目标下标
if (m_s == m_p)
{
vec.push_back(left);
}
right++;
}
return vec;
}
};
76. 最小覆盖子串
m_t哈希表存储要覆盖的字符串t的字符
m_s存储滑动窗口每次滑动时添加的字符及次数
判断m_s是否含有m_t,即s字符串包含t字符串,如果它包含,那么进行接下来的二次更新
二次更新:左边界left右移,m_s哈希表存储的这个left字符计数减一,为0时则直接删除,进而达到缩小窗口的目的。同时,每缩小一次,我们记录此时的长度len和开始位置start,我们的len要取得每次的最小值,则len就是最小覆盖子串的长度,start就是其起始下标。
class Solution {
private:
unordered_map m_s;
unordered_map m_t;
public:
bool check()
{
for (auto& x : m_t)
{
if (m_s[x.first] < x.second)
{
return false;
}
}
return true;
}
string minWindow(string s, string t) {
//1. 确定维护的变量,是一个哈希表,存储t中的字符
for (auto& x : t)
{
m_t[x]++;
}
string res;
int len = INT_MAX; //记录字符串长度
int start = -1; //目标字符串序列位置
//2. 定义滑动窗口边界,开始滑动窗口
int left = 0, right = 0;
int n = s.size();
while (right < n)
{
//3. 合法更新:滑动窗口每移动一次,字符就放进哈希表中
m_s[s[right]]++;
//4. 非法更新:无固定长度,使用while循环,每次使得t串能够包含于s串中
//并且要尽量让s串取到《最小包含》,因此每次都更新左边界,试探是否有最小的包含,并且更新滑动窗口
while (check() && left <= right)
{
if (right - left + 1 <= len)
{
len = right - left + 1;
start = left;
}
m_s[s[left]]--;
if (m_s[s[left]] == 0)
{
m_s.erase(s[left]);
}
left++;
}
right++;
}
//5. 返回结果
return (start == -1) ? "" : s.substr(start, len);
}
};
方法二
class Solution {
public:
string minWindow(string s, string t) {
//1. 确定维护的变量,是一个哈希表,存储窗口中的字符
unordered_map m_t; // 记录t中字符
unordered_map m_s; // 维护窗口中字符
int cnt = 0; // 窗口中满足个数大于等于t中字符的字符个数
int start = -1, len = INT_MAX;// 子串开始和长度
for (auto i : t) {
m_t[i]++;
}
//2. 定义滑动窗口边界,开始滑动窗口
int l = 0, r = 0;
while(r < s.size()) {
//3. 合法更新:滑动窗口每加入一个t中元素,就加入哈希表,每等于t中元素,符合条件的字符数加一
if (m_t.find(s[r]) != m_t.end()) {
m_s[s[r]]++;
if (m_s[s[r]] == m_t[s[r]]) {
cnt++;
}
}
//4. 非法更新:无固定长度,使用while循环,每次使得t串能够包含于s串中
// 当满足不小于t中字符个数的字符数等于t中字符数时可以覆盖
while(cnt == m_t.size()) { //
if (r - l + 1 < len) {// 可以覆盖时更新长度和起始位置
len = r - l + 1;
start = l;
}
// 左边是t中字符时,如果即将删除的字符的数目刚好和t中对应字符字符数相等,删除后就不相等了,需要减一
if (m_t.find(s[l]) != m_t.end()) {
if (m_s[s[l]] == m_t[s[l]]) {
cnt--;
}
m_s[s[l]]--;
if (m_s[s[l]] == 0) {
m_s.erase(s[l]);
}
}
l++;
}
r++;
}
return len == INT_MAX ? "" : s.substr(start, len);
}
};
567. 字符串的排列
class Solution {
public:
bool checkInclusion(string s1, string s2) {
unordered_map m_s1;
unordered_map m_s2;
for (auto& x:s1)
{
m_s1[x]++;
}
int left=0,right=0;
int n1 = s1.size();
int n2 = s2.size();
while (right < n2)
{
//3. 合法更新:存储字符串到哈希表中,然后与目标哈希表存储元素进行比较
m_s2[s2[right]]++;
//4. 非法更新:滑动窗口长度固定,更新维护变量,缩短左边界
if (right > n1-1) // 此后都是减一加一
{
m_s2[s2[left]]--;
if (m_s2[s2[left]]==0)
{
m_s2.erase(s2[left]);
}
left++;
}
if (m_s2 == m_s1)
{
return true;
}
right++;
}
return false;
}
};
713. 乘积小于 K 的子数组
class Solution {
public:
int numSubarrayProductLessThanK(vector& nums, int k) {
int res = 0;
//1. 确定维护的变量,乘积结果
int cst = 1;
//2. 定义滑动窗口边界,开始滑动窗口
int left = 0, right = 0;
int n = nums.size();
while (right < n)
{
//3. 合法更新:滑动窗口每移动一次,就乘一次
cst *= nums[right];
//4. 非法更新:无固定长度,使用while循环,乘积大于等于k时收缩窗口
while (left <= right && cst >= k)
{
cst /= nums[left];
left++;
}
// 满足条件,更新结果
if (cst < k)
{
res += right - left + 1;
}
right++;
}
//5. 返回结果
return res;
}
};