文章目录
本篇是优选算法之滑动窗口算法,该算法常用于求一个移动的区间
1.概念解析
🚩什么是滑动窗口算法?
把一个较长的序列(比如数组、字符串等),划分成一个个固定长度或者动态长度的 "子序列",这个子序列就被称作窗口 。好比通过一个固定大小的窗框在一幅长画卷上逐步移动,每次窗框圈定的部分就是一个窗口内容,窗口会按照特定的规则在序列上 "滑动",常见的是每次移动一个元素的位置,新元素进入窗口,同时最靠前的旧元素移出窗口,借此不断更新窗口内的数据集合
2.长度最小的子数组
✏️题目描述:

✏️示例:

传送门: 长度最小的子数组
题解:
💻第一步:
以示例1为例子,如果使用暴力枚举,那么从 2 开始一直向后扩展区间找子集,然后再从开始以此往复,所有的子数组和都枚举一遍显然十分冗余,时间复杂度为O(n²)

说明我们要减少不必要的子数组来优化,如果使用双指针那样异侧指针的话,从两侧缩小来找子集会漏掉一些情况,所以可以考虑同侧指针结合单调性来解决问题
💻第二步:

还是 left 和 right ,都从索引为 0 开始,right 一直向右移动直到该区间的和大于 target 停止,然后 left 一直向右移动寻找是否还有能满足大于 target 的区间,直到小于等于 target 为止,以此往复就能找到所有有效的区间,更新结果的位置是不确定的,要根据题意来

具体流程大致如图,元素进入区间,循环判断,元素出区间
💻细节问题
代码用了两层循环似乎是O(n²),但实际上right和left分别往后遍历数组的时间复杂度为O(n+n) = O(2n) = O(n)
💻代码实现:
cpp
#include <iostream>
#include <vector>
using namespace std;
class Solution
{
public:
int minSubArrayLen(int target, vector<int>& nums)
{
int n = nums.size(), sum = 0, len = INT_MAX;
for (int left = 0, right = 0; right < n; ++right)
{
sum += nums[right];
while (sum >= target)
{
len = min(len, right - left + 1);
sum -= nums[left++];
}
}
return len == INT_MAX ? 0 : len;
}
};
3.无重复字符的最长数组
✏️题目描述:

✏️示例:

传送门: 无重复字符的最长数组
题解:
本题的大意为找到一段子区间每个字符都只出现一次,没有重复

💻第一步:
遇到这种求子区间优先思考用滑动窗口来解决,因为本题需要统计每个数出现的次数,判断其是否重复,索性可以利用哈希表解决重复类的问题

💻第二步:
通常滑动窗口的格式是很固定的,只有更新数据的地方需要灵活变动

先让第一个数据录入,即进窗口,判断不断循环,然后right依次向后移并不断往哈希表录入每个位置字符和更新结果,直到哈希表内某个字符的数据为2;此时left减去第一个数据,即出窗口,判断不断循环,然后不断向后移直到数据为2的字符数据变为1,再次开始更新数据
💻代码实现:
cpp
#include <iostream>
#include <string>
using namespace std;
class Solution
{
public:
int lengthOfLongestSubstring(string s)
{
int hash[200] = { 0 };
int ret = 0, n = s.size();
for (int left = 0, right = 0; right < n; ++right)
{
hash[s[right]]++;
while (hash[s[right]] > 1)
{
hash[s[left++]]--;
}
ret = max(ret, right - left + 1);
}
return ret;
}
};
4.最大连续1的个数
✏️题目描述:

✏️示例:

传送门: 最大连续1的个数
题解:
本题题意为在选取的某个子区间里能够反转0为1,只能反转k个,在此前提下找到最长的连续为1的子数组

💻第一步:
求子区间依然是以滑动窗口算法为主,不过我们要统计0出现的次数,但并不像无重复字符的最长数组那题一样需要使用哈希表来处理次数,毕竟这里只有两个数,显得有点太麻烦了,只用在right向右移动时遇到0时创建一个计数器++就行了

💻第二步:
主要的出入窗口流程如图所示

先让第一个数据录入,即进窗口,判断不断循环,然后right依次向后移并不断往计数器录入0和更新结果,直到计数器0的数据大于k;此时left减去第一个数据,即出窗口,判断不断循环,然后不断向后移直到0的出现次数小于等于k,再次开始更新数据
💻代码实现:
cpp
#include <iostream>
#include <vector>
using namespace std;
class Solution
{
public:
int longestOnes(vector<int>& nums, int k)
{
int ret, zero = 0;
int n = nums.size();
for (int left = 0, right = 0; right < n; ++right)
{
if (nums[right] == 0)
{
zero++;
}
while (zero > k)
{
if (nums[left++] == 0)
{
zero--;
}
}
ret = max(ret, right - left + 1);
}
return ret;
}
};
5.将x减到0的最小操作数
✏️题目描述:

✏️示例:

传送门: 将x减到0的最小操作数
题解:
本题的题意相对来说有点难以理解,但结合示例之后就能明白。就是每次从最左边或者最右边选取数字,让x依次减去这几个数,求减到0最少需要几个数,若没有能减到零,就返回-1

💻第一步:
显然该题如果左边拿一点数,右边拿一点数,显然是很难考虑到所有的情况的,那么我们在写算法题的时候,通常正面遇到难以解决的问题时,可以考虑反面,即正难则反

a、b区间表示在获取数过程中所得到的数,那么sum表示整个数组的和,减去a+b=x就是剩下的一段连续区间,显然根据a、b选择的数不同,sum-x的区间长度位置也会不同,潜移默化中又回到了滑动窗口的问题上
💻第二步:
因此我们只需要找到一段连续区间sum1符合sum1=target=sum-x

先统计整个数组的和,让第一个数据录入,即进窗口,判断不断循环,然后right依次向后移并不断往sum1录入数据,符合要求则更新结果,直到sum1大于target=sum-x;此时left减去第一个数据,即出窗口,判断不断循环,然后不断向后移直到sum1小于target
💻代码实现:
cpp
#include <iostream>
#include <vector>
using namespace std;
class Solution
{
public:
int minOperations(vector<int>& nums, int x)
{
int sum = 0;
for (auto e : nums)
{
sum += e;
}
int target = sum - x;
if (target < 0)
{
return -1;
}
int ret = -1, n = nums.size();
for (int left = 0, right = 0, sum1 = 0; right < n; ++right)
{
sum1 += nums[right];
while (tmp > target)
{
sum1 -= nums[left++];
}
if (sum1 == target)
{
ret = max(ret, right - left + 1);
}
}
return ret == -1 ? ret : n - ret;
}
};
希望读者们多多三连支持
小编会继续更新
你们的鼓励就是我前进的动力!
