前言:
滑动窗口其实基本原理还是双指针,但在双指针中左右指针可能会有回退操作,而滑动窗口的左右指针只会向前走,不会回退,下面就来讲解一下滑动窗口的概念和具体操作(主要是例题讲解)
目录
一、什么是滑动窗口?
滑动窗口是一种动态数据结构,它包含一系列元素,这些元素按照一定的顺序排列。滑动窗口的特点是窗口的大小可以动态调整,窗口中的元素可以向前或向后滑动。
下面我们通过一道例题来具体的看一下滑动窗口是什么:
力扣209
给定一个含有
n
个正整数的数组和一个正整数target
。找出该数组中满足其总和大于等于
target
的长度最小的子数组
[numsl, numsl+1, ..., numsr-1, numsr]
,并返回其长度。如果不存在符合条件的子数组,返回0
。示例 1:
输入:target = 7, nums = [2,3,1,2,4,3] 输出:2 解释:子数组 [4,3]是该条件下的长度最小的子数组。示例 2:
输入:target = 4, nums = [1,4,4] 输出:1示例 3:
输入:target = 11, nums = [1,1,1,1,1,1,1,1] 输出:0提示:
1 <= target <= 109
1 <= nums.length <= 105
1 <= nums[i] <= 105
理解完题意之后我们就来看一下下面的讲解:
以上就是滑动窗口的定义,根据滑动窗口的定义我们需要知道在使用滑动窗口必须是左右指针都不会回退,一起向前的才可以
二、滑动窗口的原理
- 窗口大小:滑动窗口的大小是指窗口中元素的数量。窗口大小可以是固定的,也可以是动态变化的。
- 窗口位置:滑动窗口的位置是指窗口在数据序列中的起始位置。
- 窗口滑动:当窗口向前或向后滑动时,窗口中的元素会发生变化。滑动窗口的核心思想是利用窗口中的元素进行计算或分析。
三、滑动窗口的算法实现
-
简单滑动窗口:假设窗口大小为k,数据序列为S,滑动窗口算法如下:
- 初始化窗口位置为0,窗口大小为k。
- 遍历数据序列S,计算窗口中的元素和。
- 当窗口向前滑动时,更新窗口中的元素和。
- 输出窗口中的元素和。
-
动态窗口大小:当窗口大小动态变化时,需要根据实际情况调整算法。(上面图中我们举的例子就是一个动态滑动窗口)以下是一个简单的示例:
- 初始化窗口位置为0,窗口大小为k。
- 遍历数据序列S,计算窗口中的元素和。
- 当窗口向前滑动时,根据需要调整窗口大小,并更新窗口中的元素和。
- 输出窗口中的元素和。
四、滑动窗口的例题讲解
4.1. 长度最小的子数组
这个就是上面的那个例题,算法原理我们已经讲过了,现在我们直接来看一下它的实现:
int minSubArrayLen(int target, vector<int>& nums) {
int n = nums.size(), sum = 0, ret = INT_MAX;
for (int left = 0, right = 0; right < n; right++)
{
sum += nums[right]; //进窗口
while (sum >= target)
{
ret = min(ret, right - left + 1);
sum -= nums[left++]; //出窗口
}
}
return ret == INT_MAX ? 0 : ret;
}
通过这段代码我们可以看到滑动窗口一般分为这几步:
1、循环(一般是当right走到数组尾部的时候停止)
2、进窗口:在right右移时会有新的元素进到窗口里面
3、判断+出窗口:根据题意判断是否满足条件,然后让left右移让窗口中的元素出去
这几步就是滑动窗口类题的基本格式,大部分题直接套这个公式就行了,下面我们再来看几个题来巩固一下
4.2 无重复字符的最长子串
力扣3
给定一个字符串
s
,请你找出其中不含有重复字符的 最长子串 的长度。示例 1:
输入: s = "abcabcbb" 输出: 3 解释: 因为无重复字符的最长子串是"abc",所以其长度为 3。示例 2:
输入: s = "bbbbb" 输出: 1 解释: 因为无重复字符的最长子串是"b",所以其长度为 1。示例 3:
输入: s = "pwwkew" 输出: 3 解释: 因为无重复字符的最长子串是"wke",所以其长度为 3。 请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。提示:
0 <= s.length <= 5 * 104
s
由英文字母、数字、符号和空格组成
题意解析:本题要求的就是一个字符串中的不重复的最长字串,题意并不难理解,值得我们思考的有一点,就是当新元素进窗口时,我们如何可以知道它窗口中已经有这个元素呢?相信你心中已经有了答案,没错,就是借助哈希表来标记窗口中已经有过的字符,且当元素种类较少时,我们可以直接借助一个整形数组来模拟哈希表,下面我们先来看一下本题的推导过程,然后再看一下代码实现:
推导过程:
代码实现:
int lengthOfLongestSubstring(string s) {
int hash[128] = { 0 }; //使用数组来模拟哈希表
int left = 0, right = 0, n = s.size();
int ret = 0;
while (right < n)
{
hash[s[right]]++;
while (hash[s[right]] > 1)
{
hash[s[left++]]--; //出窗口
}
ret = max(ret, right - left + 1); //更新结果
right++; //让下一个元素进入窗口
}
return ret;
}
4.3 找到字符串中所有字母异位词
力扣438
给定两个字符串
s
和p
,找到s
中所有p
的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。异位词 指由相同字母重排列形成的字符串(包括相同的字符串)。
示例 1:
输入: s = "cbaebabacd", p = "abc" 输出: [0,6] 解释: 起始索引等于 0 的子串是 "cba", 它是 "abc" 的异位词。 起始索引等于 6 的子串是 "bac", 它是 "abc" 的异位词。示例 2:
输入: s = "abab", p = "ab" 输出: [0,1,2] 解释: 起始索引等于 0 的子串是 "ab", 它是 "ab" 的异位词。 起始索引等于 1 的子串是 "ba", 它是 "ab" 的异位词。 起始索引等于 2 的子串是 "ab", 它是 "ab" 的异位词。提示:
1 <= s.length, p.length <= 3 * 104
s
和p
仅包含小写字母
本题往简单点看就是找到字符串中的特定字串,只是并不要求顺序一致,这道题也是需要结合哈希表来实现的,跟上面的做题格式基本一致,下面我们直接来看一下代码实现:
vector<int> findAnagrams(string s, string p) {
vector<int> ret;
int hash1[26] = { 0 }; //统计字符串p中每个字符出现的个数
for (auto e : p) hash1[e - '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];
if (++hash2[in - 'a'] <= hash1[in - 'a']) count++; //进窗口+维护count
if (right - left + 1 > m)
{
char out = s[left++];
if (hash2[out - 'a']-- <= hash1[out - 'a']) count--; //出窗口+维护count
}
//更新结果
if (count == m) ret.push_back(left);
}
return ret;
}
感谢各位大佬观看,创作不易,还望各位大佬点赞支持!!!