达州市网站建设_网站建设公司_Sketch_seo优化
2025/12/26 16:25:21 网站建设 项目流程

一.什么是滑动窗口

        想象一下,你正在透过一个固定大小的窗口观察一条长长的数据序列,这个窗口可以左右滑动,让你看到序列的不同部分——这就是滑动窗口算法的直观理解。

        滑动窗口是一种用于处理数组/字符串子区间问题的优化技巧。它通过维护一个窗口(通常是连续的子数组或子字符串),在遍历过程中动态地调整窗口的左右边界,从而高效地解决问题。

二.为什么需要滑动窗口

让我们先来看一个经典问题:

给定一个字符串,找出其中不含有重复字符的最长子串的长度。

暴力解法:枚举所有子串,检查是否重复

// 时间复杂度:O(n³) 或 O(n²)
bool hasDuplicate(string s, int start, int end) {unordered_set seen;for (int i = start; i <= end; i++) {if (seen.count(s[i])) return true;seen.insert(s[i]);}return false;
}
int bruteForce(string s) {int maxLen = 0;for (int i = 0; i < s.length(); i++) {for (int j = i; j < s.length(); j++) {if (!hasDuplicate(s, i, j)) {maxLen = max(maxLen, j - i + 1);}}}return maxLen;
}

这种解法在长字符串面前会变得极其缓慢。而滑动窗口算法可以在O(n)时间内解决这个问题

三.核心思想

        滑动窗口通过维护一个窗口(连续的子数组/子字符串),在遍历过程中动态调整窗口的左右边界,避免重复计算。

        窗口中可以直接访问到的值一般就是目标值,将嵌套多层for循环问题转化为单次遍历,将时间复杂度从 O(n²) 降低到 O(n)。

四.使用场景

        (1)关于连续子数组/子字符串

        (2)要求找到满足某些条件的最长/最短子区间

        (3)统计满足条件的子区间个数

五.滑动窗口的类型

滑动窗口也分为两种类型,一种是窗口定长的,还有一种是窗口长度可变的

(1)定长窗口

        例题:大小为k的子数组的最大平均值

double findMaxAverage(vector& nums, int k) {double windowSum = 0;// 初始化第一个窗口for (int i = 0; i < k; i++) {windowSum += nums[i];}double maxSum = windowSum;// 滑动窗口for (int i = k; i < nums.size(); i++) {//每次移动时将左边界元素减去,将右边界元素加上windowSum += nums[i] - nums[i - k];//判断最大值maxSum = max(maxSum, windowSum);}return maxSum / k;
}

(2)不定长窗口:

        不定长窗口相对定长窗口来说更加灵活,可以动态的变化窗口大小,满足多种情况,但是也更需要判断何时收缩,可能因为一个小小的误判,就会导致整个窗口中的元素变为无效

力扣209:长度最小的子数组

给定一个含有 n个正整数的数组和一个正整数 target 。

找出该数组中满足其总和大于等于target的长度最小的 子数组[numsl, numsl+1, ..., numsr-1, numsr] ,并返回其长度如果不存在符合条件的子数组,返回 0 。

示例 1:

[4,3]

示例 2:

输入:target = 4, nums = [1,4,4]
输出:1

示例 3:

输入:target = 11, nums = [1,1,1,1,1,1,1,1]
输出:0

思路:

        动态维护一个滑动窗口,当窗口内元素和小于target时,移动右边界,扩大窗口,当窗口内元素和大于等于target时,就尝试收缩左边界,直到元素和再次小于target,重复这个过程,直到遍历完整个数组

class Solution {
public:int minSubArrayLen(int target, vector& nums) {int l = 0,sums = 0;//初始化左边界以及元素和int minl = INT_MAX;//记录最小子数组长度for(int i = 0;i=target){minl = min(minl,i-l+1);//更新最小长度sums -= nums[l];//将收缩后移出子数组的元素减去l++;//左边界收缩}}//判断有没有找到最小子数组if(minl == INT_MAX){return 0;}else{return minl;}}
};

六.滑动窗口的综合使用

滑动窗口也可以和其他数据结构一起使用,以解决更多的类型的题目

(1)滑动窗口+哈希表

力扣3:无重复字符的最长子串

给定一个字符串 s ,请你找出其中不含有重复字符的 最长 子串的长度。

示例 1:

"abc"

示例 2:

"b"

示例 3:

"wke"

思路:

        用一个哈希表去记录滑动窗口中的数字,每遍历到一个新的数字时,若其在哈希表中说明重复,反之则不重复,这也是处理重复数据问题最常用的方法.  

class Solution {
public:int lengthOfLongestSubstring(string s) {//哈希表unordered_mapmp;int left = 0;//左边界int maxLen = 0;//最大长度//遍历数组for(int i = 0;i=left){//将左边界直接更新到该元素最后出现位置的后一位,避免多次多余判断left = mp[s[i]]+1;}//将数据加入哈希表中mp[s[i]]=i;maxLen = max(maxLen,(i-left+1));//更新最大长度}return maxLen;}
};

力扣904:水果成篮

你正在探访一家农场,农场从左到右种植了一排果树。这些树用一个整数数组 fruits 表示,其中 fruits[i] 是第 i 棵树上的水果 种类 。

你想要尽可能多地收集水果。然而,农场的主人设定了一些严格的规矩,你必须按照要求采摘水果:

  • 你只有 两个 篮子,并且每个篮子只能装 单一类型 的水果。每个篮子能够装的水果总量没有限制。
  • 你可以选择任意一棵树开始采摘,你必须从 每棵 树(包括开始采摘的树)上 恰好摘一个水果 。采摘的水果应当符合篮子中的水果类型。每采摘一次,你将会向右移动到下一棵树,并继续采摘。
  • 一旦你走到某棵树前,但水果不符合篮子的水果类型,那么就必须停止采摘。

给你一个整数数组 fruits ,返回你可以收集的水果的 最大 数目。

示例 1:

输入:fruits = [1,2,1]
输出:3
解释:可以采摘全部 3 棵树。

示例 2:

输入:fruits = [0,1,2,2]
输出:3
解释:可以采摘 [1,2,2] 这三棵树。
如果从第一棵树开始采摘,则只能采摘 [0,1] 这两棵树。

示例 3:

输入:fruits = [1,2,3,2,2]
输出:4
解释:可以采摘 [2,3,2,2] 这四棵树。
如果从第一棵树开始采摘,则只能采摘 [1,2] 这两棵树。

示例 4:

输入:fruits = [3,3,3,1,2,1,1,2,3,3,4]
输出:5
解释:可以采摘 [1,2,1,1,2] 这五棵树。

思路:

        使用一个哈希表去存储篮子中已有的水果种类,当大于2时,持续收缩左边界,直到种类重新小于2

class Solution {
public:int totalFruit(vector& fruits) {//初始化unordered_mapmp;int maxNum = 0;int left = 0;//遍历数组for(int i = 0;i2){//收缩左边界,将移出的种类的水果数量-1mp[fruits[left]]--;//更新左边界left++;//当某种水果数量为0时,删除这种种类if(mp[fruits[left]]==0){mp.erase(fruits[left]);}}//记录满足要求的窗口的长度maxNum = max(maxNum,(i-left+1));}return maxNum;}
};

(2)滑动窗口+特殊队列

力扣239:滑动窗口最大值

        给你一个整数数组 nums,有一个大小为 k的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。

        返回 滑动窗口中的最大值 

示例 1:

输入:nums = [1,3,-1,-3,5,3,6,7], k = 3

输出:[3,3,5,5,6,7]

解释:滑动窗口的位置     最大值

---------------                         ----

[1 3 -1] -3 5 3 6 7                3

1 [3 -1 -3] 5 3 6 7                3

1 3 [-1 -3 5] 3 6 7                5

1 3 -1 [-3 5 3] 6 7                5

1 3 -1 -3 [5 3 6] 7                6

1 3 -1 -3 5 [3 6 7]                7

思路:

        通过维护一个单调双端队列(deque)来达到快速找到最大值的目的

        先遍历前k个元素,用一个单调队列找到最大值,并将最大值存入结果数组中.

        再从第k个元素开始遍历,每次遍历都将该数存入队列中,并维护队列的单调性,确保队列中的头部元素即为该窗口的最大值

        通过一个left变量控制滑动窗口的左边界,每次循环将其+1,如果发现左边界的值就是队列头部元素的值,说明最大值要被移出窗口了,就把队列头部元素删除

        由于我们维护的是单调队列,在没有更大的元素进入窗口时,删去头部元素后,新的头部元素仍然是窗口的最大值

class Solution {
public:vector maxSlidingWindow(vector& nums, int k) {if(nums.size()<=1){return nums;}dequeque;//双端队列,可以删除头部也可以删除尾部que.push_back(nums[0]);vectorvec;//结果数组//遍历前k个元素并维护单调队列for(int i = 1;ique.back()){que.pop_back();}que.push_back(nums[i]);//双端队列的插入}//将第一个滑动窗口的最大值存入结果数组中vec.push_back(que.front());//记录左边界int left = 0;//从第k个元素开始遍历for(int i = k;ique.back()){que.pop_back();}que.push_back(nums[i]);//判断最大值会不会随着左边界收缩而被移出窗口,如果会就把最大值从队列中删除if(nums[left]==que.front()){que.pop_front();}left++;//左边界收缩vec.push_back(que.front());//将最大值存入结果数组}return vec;}
};

七.总结

滑动窗口算法之所以强大,在于它:

  1. 高效:将O(n²)优化到O(n)

  2. 直观:符合人类的思维习惯

  3. 通用:有固定的模板可以套用

  4. 灵活:适用于各种变种问题

希望这篇指南能帮助你掌握这个强大的算法技巧!加油!!!

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询