本期,我给大家带来的是关于滑动窗口类算法的介绍,并通过具体的题目帮助大家思考理解。
目录
(一)基本概念
(二)题目讲解
1、难度:medium
1️⃣长度最小的子数组
2️⃣找到字符串中所有字⺟异位词
2、难度:hard
1️⃣最⼩覆盖⼦串
2️⃣串联所有单词的⼦串
总结
滑动窗口算法(Sliding Window Algorithm)是一种常见的算法技巧,用于解决一些数组或字符串相关的问题。该算法基于窗口的概念,在一个固定大小的窗口内移动,并根据具体问题进行适当的调整和计算。
下面将详细的介绍滑动窗口算法的工作原理和应用场景:
工作原理:
应用场景:
滑动窗口算法的步骤通常如下:
接下来,我们通过几道题目让大家具体的感受一下。(题目由易到难)
题⽬链接:209. 长度最小的子数组
【题⽬描述】
【解法】(滑动窗⼝)
算法思路:
由于此问题分析的对象是「⼀段连续的区间」,因此可以考虑「滑动窗」的思想来解决这道题。
让滑动窗⼝满⾜:从 i 位置开始,窗⼝内所有元素的和⼩于target (那么当窗内元素之和
第⼀次⼤于等于⽬标值的时候,就是 i 位置开始,满⾜条件的最⼩⻓度)。
做法:将右端元素划⼊窗⼝中,统计出此时窗⼝内元素的和:
【原理解释】
很多题解以及帖⼦只给你说怎么做,没给你解释为什么这么做。即为何滑动窗⼝可以解决问题,并且时间复杂度更低?
▪ 这个窗⼝寻找的是:以当前窗⼝最左侧元素(记为 left1 )为基准,符合条件的情况。也
就是在这道题中,从 left1 开始,满⾜区间和 sum >= target 时的最右侧(记为
right1 )能到哪⾥。
▪ 我们既然已经找到从 left1 开始的最优的区间,那么就可以⼤胆舍去 left1 。但是如
果此时重新开始统计第⼆个元素( left2 )往后的和,势必会有⼤量重复的计算(因为我们在求第⼀段区间的时候,已经算出很多元素的和了,这些和是可以在计算下次区间和的时候⽤上的)。
▪ 此时, rigth1 的作⽤就体现出来了,我们只需将 left1 这个值从 sum 中剔除。从
right1 这个元素开始,往后找满⾜ left2 元素的区间(此时 right1 也有可能是满⾜的,因为 left1 可能很⼩。 sum 剔除掉 left1 之后,依旧满⾜⼤于等于target )。这样我们就能省掉⼤量重复的计算。
▪ 这样我们不仅能解决问题,⽽且效率也会⼤⼤提升
【算法实现】
class Solution {
public:
int minSubArrayLen(int target, vector& 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;
}
};
【结果展示】
【性能分析】
具体的性能分析如下:
题⽬链接:438. 找到字符串中所有字母异位词
【题⽬描述】
【解法】(滑动窗⼝+ 哈希表)
算法思路:
【算法实现】
class Solution {
public:
vector findAnagrams(string s, string p) {
vector ret;
int hash1[26] = { 0 }; // 统计字符串 p 中每个字符出现的个数
for(auto ch : p)
hash1[ch - 'a']++;
int hash2[26] = { 0 }; // 统计窗⼝⾥⾯的每⼀个字符出现的个数
int m = p.size();
for(int left = 0, right = 0, count = 0; right < s.size(); right++)
{
char in = s[right];
// 进窗⼝ + 维护 count
if(++hash2[in - 'a'] <= hash1[in - 'a'])
count++;
if(right - left + 1 > m) // 判断
{
char out = s[left++];
// 出窗⼝ + 维护 count
if(hash2[out - 'a']-- <= hash1[out - 'a'])
count--;
}
// 更新结果
if(count == m)
ret.push_back(left);
}
return ret;
}
};
【结果展示】
【性能分析】
具体的性能分析如下:
链接如下:76. 最小覆盖子串
【题⽬描述】
【解法】 (滑动窗⼝+哈希表)
算法思路:
◦ 研究对象是连续的区间,因此可以尝试使⽤滑动窗⼝的思想来解决。
◦ 如何判断当前窗⼝内的所有字符是符合要求的呢?
【算法流程】
a. 定义两个全局的哈希表: 1 号哈希表 hash1 ⽤来记录⼦串的信息, 2 号哈希表 hash2 ⽤来记录⽬标串 t 的信息;
b. 实现⼀个接⼝函数,判断当前窗⼝是否满⾜要求:
i. 遍历两个哈希表中对应位置的元素:
• 如果 t 中某个字符的数量⼤于窗⼝中字符的数量,也就是 2 号哈希表某个位置⼤于1 号哈希表。说明不匹配,返回 false ;
• 如果全都匹配,返回 true
主函数中:
a. 先将 t 的信息放⼊ 2 号哈希表中;
b. 初始化⼀些变量:左右指针: left = 0,right = 0 ;⽬标⼦串的⻓度: len =
INT_MAX ;⽬标⼦串的起始位置: retleft ;(通过⽬标⼦串的起始位置和⻓度,我们就
能找到结果)
c. 当 right ⼩于字符串 s 的⻓度时,⼀直下列循环:
• 如果满⾜条件:
◦ 判断当前窗⼝是否变⼩。如果变⼩:更新⻓度 len ,以及字符串的起始位置
retleft ;
◦ 判断完毕后,将左侧元素滑出窗⼝,顺便更新 1 号哈希表;
◦ 重复上⾯两个过程,直到窗⼝不满⾜条件;
d. 判断 len 的⻓度是否等于 INT_MAX :
【算法实现】
class Solution {
public:
string minWindow(string s, string t) {
int hash1[128] = { 0 }; // 统计字符串 t 中每⼀个字符的频次
int kinds = 0; // 统计有效字符有多少种
for(auto ch : t)
if(hash1[ch]++ == 0) kinds++;
int hash2[128] = { 0 }; // 统计窗⼝内每个字符的频次
int minlen = INT_MAX, begin = -1;
for(int left = 0, right = 0, count = 0; right < s.size(); right++)
{
char in = s[right];
if(++hash2[in] == hash1[in]) count++; // 进窗⼝ + 维护 count
while(count == kinds) // 判断条件
{
if(right - left + 1 < minlen) // 更新结果
{
minlen = right - left + 1;
begin = left;
}
char out = s[left++];
if(hash2[out]-- == hash1[out]) count--; // 出窗⼝ + 维护 count
}
}
if(begin == -1)
return "";
else
return s.substr(begin, minlen);
}
};
【结果展示】
【性能分析】
具体的性能分析如下:
空间复杂度:哈希表hash1和hash2的存储空间为128个元素的数组,因此空间复杂度为O(1)。
链接如下:30. 串联所有单词的子串
【题⽬描述】
【解法】
算法思路:
如果我们把每⼀个单词看成⼀个⼀个字⺟,问题就变成了找到「字符串中所有的字⺟异位词」。⽆
⾮就是之前处理的对象是⼀个⼀个的字符,我们这⾥处理的对象是⼀个⼀个的单词。
因此,这道题就留给大家自己做了!!
以上就是本期关于滑动窗口算法的主要内容讲解了。感谢大家的观看与支持!!!