算法之滑动窗口
(一) 滑动窗口的定义
滑动窗口算法是一种高效处理数组 / 字符串中连续子序列(子数组、子字符串) 问题的技巧,滑动窗口算法是同向双指针(同向双指针在上一篇文章中,链接)算法的一个典型应用场景。核心思想是通过维护一个 “窗口”(由左右两个指针界定的连续区间),在遍历过程中动态调整窗口的边界,避免重复计算,从而将原本可能需要嵌套循环的 O (n²) 时间复杂度优化为 O (n)。
核心特点
- 窗口是连续的:窗口内的元素必须是原序列中连续的一段(如数组的子数组、字符串的子字符串)。
- 动态调整边界:通过移动左指针(缩小窗口)或右指针(扩大窗口),覆盖所有可能的有效子序列。
- 减少重复计算:避免对同一元素进行多次无效检查,仅关注窗口内的元素变化。
主要类型
根据窗口大小是否固定,分为两类:
1. 固定大小窗口
- 窗口长度固定(如长度为 k),左右指针同时移动(右指针右移时,左指针也同步右移,保持窗口长度不变)。
2. 可变大小窗口
- 窗口长度不固定,需根据条件动态调整(通常右指针先扩大窗口,满足条件后左指针再缩小窗口,寻找最优解)。
基本步骤(以可变窗口为例)
- 初始化左指针left=0,右指针right=0,用于界定窗口[left, right]。
- 移动右指针right,扩大窗口,直到窗口满足目标条件(如包含所有需要的元素)。
- 当窗口满足条件时,尝试移动左指针left,缩小窗口,同时更新最优解(如最短长度、最大长度等)。
- 重复步骤 2-3,直到右指针遍历完整个序列。
题目:长度最小的子数组
链接:209. 长度最小的子数组 - 力扣(LeetCode)
给定一个含有 n
个正整数的数组和一个正整数 target
。
找出该数组中满足其总和大于等于 target
的长度最小的 子数组 [numsl, numsl+1, ..., numsr-1, numsr]
,并返回其长度。如果不存在符合条件的子数组,返回 0
。
核心思想:
1. 暴力解法
采用「顺序遍历」的方式,将数组中每个元素依次作为起始点。从该起始点开始向后查找,确定满足区间和「大于等于」目标值的最短子数组。最终在所有可能的起始点对应的结果中,选取长度最小的子数组作为解。
但是时间复杂度为 O (n²),效率太低。
2. 优化解法(滑动窗口)
滑动窗口解法的核心思路是:利用「连续区间」的特性,通过两个指针(左指针 left、右指针 right)维护一个动态变化的 “窗口”,高效寻找满足条件的最短子数组,避免暴力解法中的重复计算。
具体做法:1. 初始化窗口
- 左指针 left 和右指针 right 都从 0 开始,窗口为[left, right],用变量 sum 记录窗口内元素的和。
2. 扩大窗口
- 先移动右指针,将元素加入窗口,累加 sum。此时窗口不断向右扩展,直到 sum >= target(窗口内元素和满足条件)。
3. 缩小窗口找最优解:
- 当 sum >= target 时,说明当前窗口是一个有效解。但我们要找 “最短” 的子数组,因此尝试移动左指针,将窗口左侧的元素移除(sum 减去该元素),同时更新最小长度。
(这一步的关键:因为数组元素都是正数,移除左侧元素后,sum 可能仍然满足 >=target,但窗口长度更短,所以需要持续缩小窗口直到 sum < target 为止)。
4. 重复扩展与缩小
- 右指针继续向右移动,重复步骤 2-3,直到右指针遍历完整个数组。
为什么滑动窗口效率更高?
暴力解法中,每个起始位置都要重新累加元素和,存在大量重复计算(比如从 i=0 开始计算过 [0,3] 的和,从 i=1 开始又要重新计算 [1,3] 的和)。而滑动窗口中:
- 右指针和左指针都只单向移动(从不回退),每个元素最多被加入窗口一次、移出窗口一次,总共操作次数为 O (n)。
- 窗口的 sum 是基于上一次的计算结果更新的(加右元素或减左元素),避免了重复累加,因此时间复杂度降至 O (n)。
最终,通过这种 “先扩展找可行解,再缩小找最优解” 的方式,能高效找到满足条件的最短子数组长度;若不存在则返回 0。
代码:
1. 暴力解法
//长度最小的子数组暴力解法
class Solution {
public:int minSubArrayLen(int target, vector<int>& nums) {int n = nums.size();int len = INT_MAX;for (int i = 0; i < n; i++){int sum = 0;for (int j = i; j < n; j++){sum += nums[j];if (sum >= target){len = min(len, j - i + 1);break;}}}return len == INT_MAX ? 0 : len;}
};
2. 优化解法(滑动窗口)
//长度最小的子数组优化解法
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;}
};
以上就是算法之滑动窗口的学习一点点,后续的会继续更新这个算法的题目,我们将留待日后进行。希望这些知识能为你带来帮助!如果觉得内容实用,欢迎点赞支持~ 若发现任何问题或有改进建议,也请随时与我交流。感谢你的阅读!(因为博主上学了不太方便一次性更完有点太耗费时间)