文章目录
本篇是优选算法之滑动窗口算法
,该算法常用于求一个移动的区间
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;
}
};
希望读者们多多三连支持
小编会继续更新
你们的鼓励就是我前进的动力!
