一、长度最小的子数组
1.链接
209. 长度最小的子数组 - 力扣(LeetCode)
2.描述
3.思路
本题从暴力求解的方式去切入,逐步优化成“滑动窗口”,首先,暴力枚举出各种组合的话,我们先让一个指针指向第一个,然后往后逐一遍历区间,也就是穷举出所有的子数组,但根据题目要求,当我们穷举出大于或者等于target的区间时,右边指针再向后遍历穷举的结果将没有意义,所以不需要再去往后走,而是进行下一轮遍历(前提是本题中数据全是正整数),当左指针往后走一步后,穷举的思路需要我们从头再走一次重复的数据,此时我们对其进行优化,定义一个sum值去记录区间数据,就可以不需要再次遍历一次,经过优化后,我们会发现,解题思路就变成了:
定义两个左右指针,一起从头往前走,并且记录两个指针区间的和sum,right指针往后,当大于等于目标值时,则停下记录长度,然后left走一步,进行判断是否仍然满足大于等于目标值,若是满足则让left继续走,当长度出现比原先短的区间时,进行更新最小区间的长度,left走到小于目标值时,让right进行往前走,直到right遍历完一遍数组,这种两个指针同向一起走的思路叫做“滑动窗口”
4.参考代码
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums)
{
int left = 0;
int right = 0;
int sum = nums[0];
int len = 0;
while(right < nums.size())
{
if(sum < target)
{
++right;
if(right < nums.size())
sum+=nums[right];
}
else
{
len = len == 0 ? right-left+1 : min(len,right - left + 1);
if(len == 1) break;
sum-=nums[left++];
}
}
return len;
}
};
二、无重复字符的最长子串
1.链接
3. 无重复字符的最长子串 - 力扣(LeetCode)
2.描述
3.思路
由于是对子串进行判断,子串是一段连续的区间,所以我们可以尝试采用滑动窗口的思路解决
满足窗口滑动的条件就是:判断窗口内是否有重复字符,可以利用哈希表去进行统计
当没有重复时则right往前走,当有重复时则left往前,这个过程中记录下最长的子串
4.参考代码
class Solution
{
public:
int lengthOfLongestSubstring(string s)
{
if(s.size() == 0)
{
return 0;
}
int left = 0;
int right = 0;
int len = 0;
set<char> hash;
while(right < s.size())
{
if((hash.insert(s[right])).second)//插入成功意味着没有重复
{
right++;
len = max(len,right-left);
}
else//出现重复
{
hash.erase(s[left++]);
}
}
return len;
}
};
三、最大连续1的个数|||
1.链接
1004. 最大连续1的个数 III - 力扣(LeetCode)
2.描述
3.思路
可以将题意转化为,在一段区间内,0的数量不能超过k个,利用滑动窗口去解决
当超过k个时,left往前走,直到将最前面的0给退出窗口,没有超过时,则right往前走,不断扩大窗口,过程中记录下窗口长度最大的值
4.参考代码
class Solution {
public:
int longestOnes(vector<int>& nums, int k)
{
int left = 0;
int right= 0;
int n = nums.size();
int len = 0;
while(right < n)
{
while(right < n && (k != 0 || nums[right] == 1))
{
if(nums[right] == 0)
{
k--;
}
right++;
len = max(len,right-left);
}
while(left < n && k == 0)
{
if(nums[left++] == 0)
k++;
}
}
return len;
}
};
四、将x减到0的最小操作数
1.链接
1658. 将 x 减到 0 的最小操作数 - 力扣(LeetCode)
2.描述
3.思路
我们可以先将问题转换成,在数组内找到一段最长连续的子区间之和会刚好等于数组之和减去x的值
将问题变成在数组中找一段最长的连续子区间之和等于目标值的问题后,我们就可以使用滑动窗口的思路去解决,思路可以参考第一题“长度最小的子数组”
4.参考代码
class Solution
{
public:
int minOperations(vector<int>& nums, int x)
{
int n = nums.size();
int sum = 0;
for(auto n:nums)
{
sum+=n;
}
int target = sum - x;
if(target < 0) return -1;
//找到最长的子区间
int len = -1;
for(int left = 0,right = 0,s = 0; right < n;right++ )
{
s += nums[right];//进窗口
while(s > target)//出窗口
{
s-=nums[left++];
}
if(s == target)
{
len = max(len,right-left+1);
}
}
return len == -1 ? len : n-len;
}
};
五、水果成篮
1.链接
904. 水果成篮 - 力扣(LeetCode)
2.描述
3.思路
根据题意,利用滑动窗口的思路,当窗口内的水果种类超过两种时则开始出窗口,过程中记录下窗口的长度,利用哈希表去进行统计
4.参考代码
class Solution {
public:
int totalFruit(vector<int>& fruits)
{
map<int,int> hash;
int ret = 0;
for(int left = 0,right = 0;right<fruits.size();right++)
{
hash[fruits[right]]++;
while(hash.size()>2)
{
hash[fruits[left]]--;
if(hash[fruits[left]] == 0)
{
hash.erase(fruits[left]);
}
left++;
}
ret = max(ret,right-left+1);
}
return ret;
}
};
六、找到字符串中所有字母异位词
1.链接
438. 找到字符串中所有字母异位词 - 力扣(LeetCode)
2.描述
3.思路
4.参考代码
class Solution
{
public:
vector<int> findAnagrams(string s, string p)
{
int hash1[26] = {0};
int hash2[26] = {0};
for(auto c:p)
{
hash1[c-'a']++;
}
int m = p.size();
vector<int> ret;
for(int left = 0,right = 0,count = 0; right < s.size(); right++)
{
char in = s[right];
hash2[in - 'a']++;//入窗口
if(hash2[in - 'a'] <= hash1[in - 'a']) count++;//维护count
char out = s[left];
if(right-left+1 > m)//出窗口
{
if(hash2[out - 'a'] <= hash1[out - 'a']) count--;//维护count
hash2[out - 'a']--;
left++;
}
//判断目标
if(count == m)
{
ret.push_back(left);
}
}
return ret;
}
};
七、串联所有单词的子串
1.链接
30. 串联所有单词的子串 - 力扣(LeetCode)
2.描述
3.思路
4.参考代码
class Solution {
public:
vector<int> findSubstring(string s, vector<string>& words)
{
int len = words[0].size();
unordered_map<string,int> m1;
unordered_map<string,int> m2;
vector<int> ret;
for(auto& s:words) m1[s]++;
for(int i = 0;i<len;i++)//以i位置为起点
{
for(int left = i,right = i+len-1,count = 0; right < s.size() ; right+=len)//滑动窗口
{
string in = s.substr(right-len+1,len);
m2[in]++;//入窗口
if(m2[in] <= m1[in]) count++;//维护count
if(right-left+1 > len*words.size())//出窗口
{
string out = s.substr(left,len);
if(m2[out] <= m1[out]) count--;
m2[out]--;
left+=len;
}
if(count == words.size()) ret.push_back(left);
}
m2.clear();
}
return ret;
}
};
八、最小覆盖子串
1.链接
76. 最小覆盖子串 - 力扣(LeetCode)
2.描述
3.思路
有了前面的经验,不难想到这题该怎么用滑动窗口解决,根据题意分析,我们知道首先将t内的字符记录到哈希表hash1中,然后让滑动窗口的右侧不断的往前走,直到满足题目条件,即滑动窗口内的字符串包含了t内的字符,此时让left往前收缩,记录下最小的区间,然后直到不再满足条件后,让right继续往后找满足条件的子串,最终记录下最短的那个子串即可,当然,还有如何优化哈希比较的细节需要注意,以及对于如何记录下最短子串的考量
4.参考代码
class Solution {
public:
string minWindow(string s, string t)
{
int n = s.size();
int hash1[128] = {0};
int hash2[128] = {0};
for(auto& c : t) hash1[c]++;
int begin = -1; int len = s.size()+1;
for(int left = 0,right = 0,count = 0;right < n;right++)
{
char in = s[right];
hash2[in]++;
if(hash2[in] <= hash1[in]) count++;
while(count == t.size())
{
if(right-left+1 < len)
{
len = right-left+1;
begin = left;
}
char out = s[left++];
if(hash2[out] <= hash1[out]) count--;
hash2[out]--;
}
}
if(begin == -1) return "";
else return s.substr(begin,len);
}
};
总结
本章节主要整理了关于滑动窗口的算法思想,由简单到困难逐步递进,整理了八道相关例题以及思路解析提供参考,也可以通过链接去做