目录
[最大连续1的个数 III](#最大连续1的个数 III)
[将 x 减到 0 的最小操作数](#将 x 减到 0 的最小操作数)
注意:滑动窗口的本质其实就是同向的双指针
滑动窗口的使用:

长度最小的子数组

**思路:**定义 left,right 两个指针,这两个指针指向的元素以及它们中间的元素构成了一个窗口,两指针初始时都指向下标为 0 处,当窗口中数据总和小于 target 时,右移 right 增大窗口中的数据总和,直到大于等于 target 时,窗口满足题目要求,计算它的长度保存下来,然后右移 left 来减小窗口数据总和,判断是否仍然大于等于 target,如果满足要求,更新结果,不满足继续右移 right。
**为什么使用滑动窗口能解题:**首先使用暴力枚举的方式一定可以找到正确的结果,而滑动窗口就是利用数组的单调性省去了很多没有必要枚举的部分,比如当窗口内数据总和第一次大于 target 后,此时在右移 right 没有意义了,因为再去向窗口增加更大的值一定满足条件,但是元素数据增多,一定不是长度最小的子数组,所以,以下标为 0 的元素作为起始的所有情况就都已经考虑到了,只不过后面的情况一定不是答案,直接略过,然后右移 left ,以下一个元素为起始继续枚举,如果符合就更新结果,不符合就继续向窗口中增加元素。
代码:
cpp
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int left = 0;
int right = 0;
int ret = INT_MAX;
int sum = 0;
while(right < nums.size()){
sum += nums[right++];
while(sum >= target){
ret = min(ret, right - left);
sum -= nums[left++];
}
}
return ret == INT_MAX ? 0 : ret;
}
};
无重复字符的最长子串

思路:滑动窗口+哈希表(用来判断字符是否重复)。
**过程:**还是定义 left,right 两个指针,将 right 指向的元素丢入哈希表,判断是否重复,不重复就更新结果,然后继续向右移动 right 指针,让剩余字符进窗口,如果重复了,就将当前 left 指针指向的字符从哈希表中删除,并右移 left 指针,直到不重复。

代码:
cpp
class Solution {
public:
int lengthOfLongestSubstring(string s) {
int left = 0;
int right = 0;
int hash[128] = {0};
int ret = 0;
while(right < s.size()){
hash[s[right]]++;
while(hash[s[right]] > 1){
hash[s[left]]--;
left++;
}
ret = max(ret, right - left + 1);
right++;
}
return ret;
}
};
最大连续1的个数 III

思路: 本题因为有翻转 0 的限制,所以可以将问题从找连续的 1 转化为找出最长子数组,0 的个数不超过 k 个。
**过程:**定义 left,right 两个指针和一个记录窗口中 0 个数的计数器,向窗口中增加元素时判断是否为 0,如果是 0,计数器自增 1,否则不变,然后判断 0 个数是否超过 k,超过就减小窗口,直到 0 个数符合要求,当 0 个数符合要求后更新结果即可。
代码:
cpp
class Solution {
public:
int longestOnes(vector<int>& nums, int k) {
int left = 0;
int right = 0;
int ret = 0;
int count = 0;
while(right < nums.size()){
if(nums[right++] == 0) count++;
while(count > k){
if(nums[left] == 0) count--;
left++;
}
ret = max(ret, right - left);
}
return ret;
}
};
将 x 减到 0 的最小操作数

思路: 直接解题因为可能会用到数组左边的元素,也可能会用到数组右边的元素,这些数据不连续,不好解题,所以对问题进行转化。定义数组元素之和为 sum,将问题转化为找出最长子数组,其元素之和正好等于sum - x。

代码:
cpp
class Solution {
public:
int minOperations(vector<int>& nums, int x) {
int left = 0;
int right = 0;
int sum = 0;
for(auto num : nums){
sum += num;
}
int target = sum - x;
if(target < 0) return -1;
int ret = -1;
sum = 0;
while(right < nums.size()){
sum += nums[right++];
while(sum > target){
sum -= nums[left++];
}
if(sum == target){
ret = max(ret, right - left);
}
}
return ret == -1 ? -1 : nums.size() - ret;
}
};
水果成篮

**思路:**本题本质其实就是找出一个最长子数组,且子数组中不超过两种类型的水果。使用滑动窗口 + 哈希表就可以解决问题,其中哈希表的作用是确保窗口中水果种类不超过2。
代码:
cpp
class Solution {
public:
int totalFruit(vector<int>& fruits) {
int left = 0;
int right = 0;
unordered_map<int, int> hash;
int ret = 0;
while(right < fruits.size()){
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);
}
return ret;
}
};
找到字符串中所有字母异位词

思路: 滑动窗口 + 哈希表 + 利用变量count统计窗口中"有效字符"个数。
过程:
- 将 p 串放入一个哈希表中(哈希表1)。
- 入窗口:定义 left,right 两个指针,移动 right 指针向窗口增加字符,并将该字符放入另一个哈希表中(哈希表2),并且判断当前 right 指向的字符在哈希表2中的个数是否小于等于哈希表1中该字符的个数,如果小于等于,说明是有效字符,count++,否则 count 不变。
- 判断 + 出窗口:如果窗口字符数量大于 p 串长度,移动 left 指针减少窗口中字符数量,同时维护哈希表2,判断移出去的字符在移除之前在哈希表2中的数量是否大于哈希表1中的数量,如果大于,说明是多余或者无效的字符,count 不变,否则 count--。
- 更新结果:如果 count 等于 p 串的长度,说明是异位词,更新结果。
代码:
cpp
class Solution {
public:
vector<int> findAnagrams(string s, string p) {
int hash1[26] = {0};
for(auto ch : p) hash1[ch - 'a']++;
int hash2[26] = {0};
int left = 0;
int right = 0;
int count = 0;
vector<int> ret;
while(right < s.size()){
hash2[s[right] - 'a']++;
if(hash2[s[right] - 'a'] <= hash1[s[right] - 'a']) count++;
right++;
if(right - left > p.size()){
if(hash2[s[left] - 'a'] <= hash1[s[left] - 'a']) count--;
hash2[s[left++] - 'a']--;
}
if(count == p.size()){
ret.push_back(left);
}
}
return ret;
}
};
串联所有单词的子串

思路: 滑动窗口 + 哈希表 + 利用变量count统计窗口中"有效字符"个数。
**过程:**和上题非常类似,这里只说不同点。首先,哈希表维护的是字符串和数量的映射,其次,因为每次都要向哈希表中存一个字符串,所以 right,left 每次直接移动 words 中每个单词的长度个单位,最后,因为两个指针每次移动距离的增大,一次滑动窗口无法遍历所有情况,需要遍历 words中单词的长度次,例如下图 words 中每个单词长度为 3。

代码:
cpp
class Solution {
public:
vector<int> findSubstring(string s, vector<string>& words) {
unordered_map<string, int> hash1;
for(auto& s : words) hash1[s]++;
int len = words[0].size();
int m = words.size();
vector<int> ret;
for(int i = 0; i < len; i++){
int left = i;
int right = i;
int count = 0;
unordered_map<string, int> hash2;
while(right + len <= s.size()){
string str = s.substr(right, len);
hash2[str]++;
if(hash2[str] <= hash1[str]) count++;
if(right - left + len > len * m){
string out = s.substr(left, len);
if(hash2[out] <= hash1[out]) count--;
hash2[out]--;
if(hash2[out] == 0) hash2.erase(out);
left += len;
}
if(count == m) ret.push_back(left);
right += len;
}
}
return ret;
}
};
最小覆盖子串

思路:滑动窗口 + 哈希表 + 利用变量count统计窗口中"有效字符"种类。总体思路和上面两题一样,只不过这里 count 统计的是有效字符的种类,而不是数量,因为存在如下情况:

代码:
cpp
class Solution {
public:
string minWindow(string s, string t) {
int hash1[128] = { 0 };
int kinds = 0;
for(auto ch : t)
if(hash1[ch]++ == 0) kinds++;
int hash2[128] = { 0 };
int left = 0;
int right = 0;
int minlen = INT_MAX;
int begin = -1;
int count = 0;
while(right < s.size()){
char in = s[right];
hash2[in]++;
if(hash2[in] == hash1[in]) count++;
while(count == kinds){
if(right - left + 1 < minlen){
minlen = right - left + 1;
begin = left;
}
char out = s[left++];
hash2[out]--;
if(hash2[out] < hash1[out]) count--;
}
right++;
}
return begin == -1 ? "" : s.substr(begin, minlen);
}
};